本文主要是介绍Java并发之ReentrantLock详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文:
http://blog.csdn.net/lipeng_bigdata/article/details/52154637
一、入题
ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为:
那么,ReentrantLock内部是如何实现锁的呢?接下来我们就以JDK1.7中的ReentrantLock的lock()为例详细研究下。
二、ReentrantLock类的结构
ReentrantLock类实现了Lock和java.io.Serializable接口,其内部有一个实现锁功能的关键成员变量Sync类型的sync,定义如下:
而这个Sync是继承了AbstractQueuedSynchronizer的内部抽象类,主要由它负责实现锁的功能。关于AbstractQueuedSynchronizer我们会在以后详细介绍,你只要知道它内部存在一个获取锁的等待队列及其互斥锁状态下的int状态位(0当前没有线程持有该锁、n存在某线程重入锁n次)即可,该状态位也可用于其它诸如共享锁、信号量等功能。
Sync在ReentrantLock中有两种实现类:NonfairSync、FairSync,正好对应了ReentrantLock的非公平锁、公平锁两大类型。
三、获取锁主体流程
ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,其lock()获取锁的主要流程如下:
首先,ReentrantLock的lock()方法会调用其内部成员变量sync的lock()方法;
其次,sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的lock()方法,其会调用acquire()方法;
然后,acquire()方法则在sync父类AbstractQueuedSynchronizer中实现,它只有一段代码:
通过tryAcquire()方法试图获取锁,获取到直接返回结果,否则通过嵌套调用acquireQueued()、addWaiter()方法将请求获取锁的线程加入等待队列,如果成功的话,将当前请求线程阻塞,and,over!
队列如何实现及如何添加到队列中以后再做详细分析!这里只关注ReentrantLock的实现逻辑。
上述就是公平锁、非公平锁实现获取锁的主要流程,而针对每种锁来说,其实现方式有很大差别,主要就体现在各自实现类的lock()和tryAcquire()方法中。在sync的抽象类Sync及其抽象父类AbstractQueuedSynchronizer中,lock()方法和tryAcquire()方法被定义为抽象方法或者未实现,而是由具体子类去实现:
下面,我们分别研究下非公平锁和公平锁的实现。
四、非公平锁NonfairSync
1、lock()方法
通过代码可以看到,非公平锁上来就无视等待队列的存在而抢占锁,通过基于CAS操作的compareAndSetState(0, 1)方法,试图修改当前锁的状态,这个0表示AbstractQueuedSynchronizer内部的一种状态,针对互斥锁则是尚未有线程持有该锁,而>=1则表示存在线程持有该锁,并重入对应次数,这个上来就CAS的操作也是非公共锁的一种体现,CAS操作成功的话,则将当前线程设置为该锁的唯一拥有者。
抢占不成功的话,则调用父类的acquire()方法,按照上面讲的,继而会调用tryAcquire()方法,这个方法也是由最终实现类NonfairSync实现的,如下:
2、tryAcquire()
而这个nonfairTryAcquire()方法实现如下:
还是上来先判断锁的状态,通过CAS来抢占,抢占成功,直接返回true,如果锁的持有者线程为当前线程的话,则通过累加状态标识重入次数。抢占不成功,或者锁的本身持有者不是当前线程,则返回false,继而后续通过进入等待队列的方式排队获取锁。可以通过以下简单的图来理解:
五、公平锁FairSync
1、lock()
公平锁的lock()方法就比较简单了,直接调用acquire()方法,如下:
2、tryAcquire()
公平锁的tryAcquire()方法也相对较简单,如下:
当前线程会在得到当前锁状态为0,即没有线程持有该锁,并且通过!hasQueuedPredecessors()判断当前等待队列没有前继线程(也就是说,没有比我优先级更高的线程在请求锁了)获取锁的情况下,通过CAS抢占锁,并设置自己为锁的当前拥有者,当然,如果是重入的话,和非公平锁处理一样,通过累加状态位标记重入次数。
而一旦等待队列中有等待者,或当前线程抢占锁失败,则它会乖乖的进入等待队列排队等待。公平锁的实现大致如下:
六、默认实现
ReentrantLock的默认实现为非公平锁,如下:
当然,你也可以通过另外一个构造方法指定锁的实现方式,如下:
即便是公平锁,如果通过不带超时时间限制的tryLock()的方式获取锁的话,它也是不公平的,因为其内部调用的是sync.nonfairTryAcquire()方法,无论抢到与否,都会同步返回。如下:
但是带有超时时间限制的tryLock(long timeout, TimeUnit unit)方法则不一样,还是会遵循公平或非公平的原则的,如下:
其它流程都比较简单,读者可自行翻阅Java源码查看!
这篇关于Java并发之ReentrantLock详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!