深入浅出ReentrantLock

        ReentrantLock是java.util.concurrent.locks包下的类, 是基于AQS、CAS实现的可重入锁。 

     可重入锁指当前线程可以多次获得锁, 但务必要lock和unlock成对出现。 在ReentrantLock类里lock就是state从0变1、或者state自增1, unlock就是state从1变0、或者state自减1.


        如上面类图所示, ReentrantLock实现了Lock接口、只有一个成员变量sync, 并定义了3个内部类Sync、NonfairSync和FairSync; ReentrantLock类的函数都是调用sync对象实现的;

       NonfairSync、FairSync的区别在于等待线程获取锁的时序:

 1、 NonfairSync是非公平锁, 线程获取锁的顺序与调用lock的顺序无关, 是随机的。刚刚释放锁的线程可能再次拿到锁。

 2、 FairSync是公平锁,线程获取锁的顺序与调用lock的顺序一致,即First In First Out。


AbstractQueueSynchronizer介绍

       如上面类图所示,AbstractQueueSynchronizer继承于AbstractOwnableSynchronizer。 AbstractOwnableSynchronizer类很简单,只有一个成员变量和对象的set/get方法。 exclusiveOwnerThread表示独占的线程,换句话说是拿到锁的线程。


       AbstractQueueSynchronizer使用CLH(Craig Landin Hagersten)队列实现串行执行, 整个队列是个双向链表;每个CLH锁节点就是上图中的Node类, 保存了前一个节点pre和后一个节点next、当前节点对象的线程thread、状态waitStatus。 按照FIFO原则,读取时取头部节点,插入时放到队列尾部。


      需要注意的是AQS的队列头结点是“哨兵结点”, 即不关联任何线程, 只设置next成员变量。



         仔细看类图, ReentrantLock其实就是对state、head、tail、exclusiveOwnerThread参数的读写。 下面详细介绍一下这些参数的变化过程。

       

先说说公平锁:

 1、  初始化ReentrantLock后,state等于0,  表示无线程拿到锁。 这时A线程执行了lock函数并成功拿到锁, 即state等于1。


         这是exclusiveOwnerThread等于A线程的引用。


2、  A线程在运行时B线程执行lock函数, 因拿不到锁要排队。



3、A线程在执行中,如果再次调用lock会怎样? 答:线程会继续执行,state参数自增1.


      这就是可重入锁, 如果一个线程已经拿到锁了,再次获取这个锁时只是把状态值加1。  而调用unlock实际上就是state减1的过程。再次重申lock和unlock函数要成对出现!!! 否则可能出现死锁。


4、C线程申请锁, 因为A线程在执行,所以加入队列尾部。



5、 A线程释放锁后, B线程在队列首位,所以B线程拿到锁, 并在队列中移出B线程节点。



非公平锁模型:

        跟上面公平锁类似, 只是不按照队列顺序获取锁了。 每个等待线程获取锁的机会都是一样的。


下面结合代码说说细节, AQS使用了Java CAS特性修改state参数。


      lock函数就是state从0变为1, 且使用CAS解决多线程并发的互斥问题。 再看看unlock函数,其实就是state减1, 当state等于0时表示释放锁。



总结:

        ReentrantLock获取锁、释放锁其实就是通过Java CAS修改state参数值; 从0到1表示拿到锁, 从1到0表示释放锁, state等于2、3等等时表示可重入锁。

猜你喜欢

转载自blog.csdn.net/brycegao321/article/details/79376177