JAVA各种锁的优劣对比分析( 六 )


当线程尝试获取锁时 , 可重入锁先尝试获取并更新status值 , 如果status == 0表示没有其他线程在执行同步代码 , 则把status置为1 , 当前线程开始执行 。如果status != 0 , 则判断当前线程是否是获取到这个锁的线程 , 如果是的话执行status+1 , 且当前线程可以再次获取锁 。而非可重入锁是直接去获取并尝试更新当前status的值 , 如果status != 0的话会导致其获取锁失败 , 当前线程阻塞 。
释放锁时 , 可重入锁同样先获取当前status的值 , 在当前线程是持有锁的线程的前提下 。如果status-1 == 0 , 则表示当前线程所有重复获取锁的操作都已经执行完毕 , 然后该线程才会真正释放锁 。而非可重入锁则是在确定当前线程是持有锁的线程之后 , 直接将status置为0 , 将锁释放 。

JAVA各种锁的优劣对比分析

文章插图
 
6. 独享锁 VS 共享锁独享锁和共享锁同样是一种概念 。我们先介绍一下具体的概念 , 然后通过ReentrantLock和ReentrantReadWriteLock的源码来介绍独享锁和共享锁 。
独享锁也叫排他锁 , 是指该锁一次只能被一个线程所持有 。如果线程T对数据A加上排它锁后 , 则其他线程不能再对A加任何类型的锁 。获得排它锁的线程即能读数据又能修改数据 。JDK中的synchronized和JUC中Lock的实现类就是互斥锁 。
共享锁是指该锁可被多个线程所持有 。如果线程T对数据A加上共享锁后 , 则其他线程只能对A再加共享锁 , 不能加排它锁 。获得共享锁的线程只能读数据 , 不能修改数据 。
独享锁与共享锁也是通过AQS来实现的 , 通过实现不同的方法 , 来实现独享或者共享 。
下图为ReentrantReadWriteLock的部分源码:
JAVA各种锁的优劣对比分析

文章插图
 
我们看到ReentrantReadWriteLock有两把锁:ReadLock和WriteLock , 由词知意 , 一个读锁一个写锁 , 合称“读写锁” 。再进一步观察可以发现ReadLock和WriteLock是靠内部类Sync实现的锁 。Sync是AQS的一个子类 , 这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在 。
在ReentrantReadWriteLock里面 , 读锁和写锁的锁主体都是Sync , 但读锁和写锁的加锁方式不一样 。读锁是共享锁 , 写锁是独享锁 。读锁的共享锁可保证并发读非常高效 , 而读写、写读、写写的过程互斥 , 因为读锁和写锁是分离的 。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升 。
那读锁和写锁的具体加锁方式有什么区别呢?在了解源码之前我们需要回顾一下其他知识 。
在最开始提及AQS的时候我们也提到了state字段(int类型 , 32位) , 该字段用来描述有多少线程获持有锁 。
在独享锁中这个值通常是0或者1(如果是重入锁的话state值就是重入的次数) , 在共享锁中state就是持有锁的数量 。但是在ReentrantReadWriteLock中有读、写两把锁 , 所以需要在一个整型变量state上分别描述读锁和写锁的数量(或者也可以叫状态) 。于是将state变量“按位切割”切分成了两个部分 , 高16位表示读锁状态(读锁个数) , 低16位表示写锁状态(写锁个数) 。如下图所示:
JAVA各种锁的优劣对比分析

文章插图
 
了解了概念之后我们再来看代码 , 先看写锁的加锁源码:
JAVA各种锁的优劣对比分析

文章插图