看这部分的前提是大家已经看过AbstractQueuedSynchronizer这个类,知道它是个啥了哈,如果不知道,请先看这里https://www.jianshu.com/p/c629aebcd251。
咳咳,这个锁嘛,就是当程序的某个部分被多个线程执行时想让线程挨个执行它而不是大家一拥而上,所以就用锁来控制它,这个锁同时只能被一个线程占据,这样某些程序才不会因为并发而出错。
先看怎么使用,再看实现原理吧。
public classReentrantTestextendsThread {
privateReentrantLockreentrantLock;
publicReentrantTest(ReentrantLock reentrantLock){
this.reentrantLock= reentrantLock;
}
@Override
public voidrun() {
// reentrantLock.lock();
try{
for(intj =0;j <100000;j++) {
System.out.print(j);
System.out.print("\n");
}
}finally{
// reentrantLock.unlock();
}
}
public static voidmain(String[] args) {
ReentrantLock reentrantLock =newReentrantLock();
ReentrantTest reentrantTest =newReentrantTest(reentrantLock);
ReentrantTest reentrantTest1 =newReentrantTest(reentrantLock);
reentrantTest.start();
reentrantTest1.start();
}
}
这是一段简单的示例代码,目的是为了让每个线程可以完整打出0到99999,不被中断,有人会问那你干嘛用多线程…..=。=我承认我例子想得不好,反正就这样!大家试着跑一跑就能看到效果啦,如果注释掉上面那两行,程序输出的结果就是混乱的,两个线程输出的结果是交杂的,但是用了lock就会是一个线程执行完才下个线程。
这里又有人问了,那这个跟在方法前加个synchronise有啥区别?
首先,这都JDK10都要出来了,synchronise在进行了一系列优化后,性能还算OK,跟ReentrantLock差距不大了。既然性能差的不多,那就从功能上来对比,确实synchronise很方便,一切由jvm控制,但是它的粒度不如ReentrantLock细,怎么说呢,ReentrantLock是要我们自己写,从哪里lock,所以就算在一个方法内部,它也可以控制方法的一部分是受到锁的控制,而另一部分不受,所以粒度比较细,但要注意一定要在finally中释放锁,不然程序执行出现异常等提前退出的现象可能会导致死锁。
好,然后呢,ReentrantLock提供了一些额外的功能,比如可重入,可中断,可限时,可公平等,下面在讲它实现的时候会挨个讲,这些都是synchronise不具备的啦。
ReentrantLock名字叫Reentrant对吧,重新进入的意思,相信大家听说过可重入锁,这其实跟这个ReentrantLock的实现机制有关,它内部定义了一个私有类Sync(这个就是实现锁这个功能的关键):
abstractstaticclassSyncextendsAbstractQueuedSynchronizer {
//序列号
privatestaticfinallongserialVersionUID = -5179523762034025860L;
//获取锁
abstractvoidlock();
//非公平方式获取
finalbooleannonfairTryAcquire(intacquires) {
//当前线程
finalThread current =Thread.currentThread();
//获取状态
intc =getState();
if(c == 0) {//表示没有线程正在竞争该锁
if(compareAndSetState(0, acquires)) {//比较并设置状态成功,状态0表示锁没有被占用
//设置当前线程独占setExclusiveOwnerThread(current);
returntrue;//成功}
}
elseif(current == getExclusiveOwnerThread()) {//当前线程拥有该锁
intnextc = c + acquires;//增加重入次数
if(nextc < 0)//overflow
thrownewError("Maximum lock count exceeded");
//设置状态setState(nextc);
//成功
returntrue;
}
//失败
returnfalse;
}
//试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
protectedfinalbooleantryRelease(intreleases) {
intc = getState() -releases;
if(Thread.currentThread() != getExclusiveOwnerThread())//当前线程不为独占线程
thrownewIllegalMonitorStateException();//抛出异常
//释放标识
booleanfree =false;
if(c == 0) {
free=true;
//已经释放,清空独占
setExclusiveOwnerThread(null);
}
//设置标识setState(c);
returnfree;
}
//判断资源是否被当前线程占有
protectedfinalbooleanisHeldExclusively() {
//While we must in general read state before owner,
//we don't need to do so to check if current thread is owner
returngetExclusiveOwnerThread() ==Thread.currentThread();
}
//新生一个条件
finalConditionObject newCondition() {
returnnewConditionObject();
}
//Methods relayed from outer class
//返回资源的占用线程
finalThread getOwner() {
returngetState() == 0 ?null: getExclusiveOwnerThread();
}
//返回状态
finalintgetHoldCount() {
returnisHeldExclusively() ? getState() : 0;
}
//资源是否被占用
finalbooleanisLocked() {
returngetState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
//自定义反序列化逻辑
privatevoidreadObject(java.io.ObjectInputStream s)
throwsjava.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0);//reset to unlocked state}
}
好的,贴了一段代码,就是Sync的定义,注意到,它继承自AbstractQueuedSynchronizer,我讲这个是为了讲ReentrantLock的实现机制哈,你打开源码就知道这个ReentrantLock其实没别的东西,主要就是靠这个Sync,所以得把这个Sync是干嘛的讲通。好的继续,这个Sync里就有几个起主要作用的方法,下面一一说明这个东西是怎么工作的。
首先,最重要的方法:lock():
abstract void lock();
第一部分 FairSync
它是一个抽象方法,具体的实现在它FairSync和NonfairSync两个子类中(一个是公平锁一个是不公平锁
),当这个方法被调用时,意味着执行该方法的当前线程是在试图去获取这个锁,我们先看NonfairSync的lock方法:
final voidlock() {
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到compareAndSetState(0, 1)如果此刻锁的状态是0就代表了锁现在没人占,那么我们当前线程直接占了就行,否则,执行acquire方法:
public final voidacquire(intarg) {
if(!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}
这个方法在AbstractQueuedSynchronizer里面哈,让你们去看了,因为它是基础,然后这个方法里呢,tryAcquire则是在NonfairSync里实现的:
protected final booleantryAcquire(intacquires) {
returnnonfairTryAcquire(acquires);
}
原来它也就调用的父类中的nonfairTryAcquire方法:
final booleannonfairTryAcquire(intacquires) {
finalThread current = Thread.currentThread();
intc = getState();
if(c ==0) {
if(compareAndSetState(0,acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if(current == getExclusiveOwnerThread()) {
intnextc = c + acquires;
if(nextc <0)// overflow
throw newError("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
分析一波,这个参数代表可以重复的获取这个锁,State这个变量,存储的就是这个锁被占有的状态,0就是没被占有,1就是被占有一次,n就是被占有n次(这个次数可能描述不是特别恰当,自己体会~),然后加入状态是0,那么把这个state设置为acquires这么大,然后占有这个锁,假如状态不是0,那么又会去判断,占有这个锁的是不是当前线程(不是多此一举哈,因为前面说了,ReentrantLock可以重复申请,也就是可重入,那么就有可能这个锁已经被当前线程占有啦),那就把状态的值再设为当前的状态值加上acquires,如果不满足以上条件,啥都不干,返回false(返回false后接下来的操作就会回到AbstractQueuedSynchronizer里面去,也就是排队等待的那套流程,你们要看!!)
第二部分 NonFairSync
FairSync里面的lock方法更简单:
final voidlock() {
acquire(1);
}
直接执行acquire方法,我们上面写出来这个方法,所以还是会调用tryAcquire方法:
protected final booleantryAcquire(intacquires) {
finalThread current = Thread.currentThread();
intc = getState();
if(c ==0) {
if(!hasQueuedPredecessors() &&
compareAndSetState(0,acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if(current == getExclusiveOwnerThread()) {
intnextc = c + acquires;
if(nextc <0)
throw newError("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
就是当锁状态为0时,公平锁会调用hasQueuedPredecessors判断是否有其他线程比它等待更长时间,方法如下:
public final booleanhasQueuedPredecessors() {
Node t =tail;
Node h =head;
Node s;
returnh != t &&
((s = h.next) ==null|| s.thread!= Thread.currentThread());
}
来看下它的判断条件, 满足下面几个条件返回true:
首先等待队列的头节点不等于尾节点,其次,头节点的下一个节点不为空,或者当前线程不是头节点的下一个节点。
如果返回true代表当前线程节点前面还有等待的其他线程,那么为了公平当前线程就不会去占有这个锁(就是说即使这个锁现在是空着的也不会去占),不像不公平锁,它不会去判断是否队列还有更靠前的线程在等待
else if(current == getExclusiveOwnerThread()) {
intnextc = c + acquires;
if(nextc <0)
throw newError("Maximum lock count exceeded");
setState(nextc);
return true;
}
这部分跟不公平锁是一样的。
对,很简单就讲完了,自然它里面还提供了一些其他方法,不过都很简单啦,大家可以自己去看,至于unlock:
public voidunlock() {
sync.release(1);
}
简单哈,就是调用:
public final booleanrelease(intarg) {
if(tryRelease(arg)) {
Node h =head;
if(h !=null&& h.waitStatus!=0)
unparkSuccessor(h);
return true;
}
return false;
}
先调用tryRlease方法:
protected final booleantryRelease(intreleases) {
intc = getState() - releases;
if(Thread.currentThread() != getExclusiveOwnerThread())
throw newIllegalMonitorStateException();
booleanfree =false;
if(c ==0) {
free =true;
setExclusiveOwnerThread(null);
}
setState(c);
returnfree;
}
将状态设为当前状态减去releases的值,如果当前独占锁的线程不是当前线程,那么一定有毛病,抛异常!否则,如果减去releases之后的状态值为0了的话,代表该锁已经被完全释放了,那么就把这个锁的所有者设置为null(不再是当前线程)。
在tryRlease成功的情况下,如果等待队列的头既不为null并且它的等待状态不是0,那么就要调用AQS中的unparkSuccessor方法(可能会唤醒它的下一个节点),这个等待状态的说明在AQS那篇文章里也有讲哈,这里不重复了。
整个获取锁和释放锁的过程就是这样了。
第三部分 可限时
可限时和可中断我们还没讲,我相信可限时很好理解,就是获取锁的时候等待特定的时间后就不再等待直接返回,相应的方法:
public booleantryLock(longtimeout,TimeUnit unit)
throwsInterruptedException {
returnsync.tryAcquireNanos(1,unit.toNanos(timeout));
}
第四部分 可中断
可中断有什么用呢?就是它能够及时地响应中断信号(可能从别的线程发送过来的中断信号),ReentrantLock里面的可中断方法是:
public voidlockInterruptibly()throwsInterruptedException {
sync.acquireInterruptibly(1);
}
走进acquireInterruptibly方法:
public final voidacquireInterruptibly(intarg)
throwsInterruptedException {
if(Thread.interrupted())
throw newInterruptedException();
if(!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
其实就是判断当前的Thread的中断信号,如果是被中断的状态就直接抛异常,就不会继续去tryAcquire了,否则还是会去等待获取锁,跟lock()一样。
看看doAcquireInterruptibly方法:
private voiddoAcquireInterruptibly(intarg)
throwsInterruptedException {
finalNode node = addWaiter(Node.EXCLUSIVE);
booleanfailed =true;
try{
for(;;) {
finalNode p = node.predecessor();
if(p ==head&& tryAcquire(arg)) {
setHead(node);
p.next=null;// help GC
failed =false;
return;
}
if(shouldParkAfterFailedAcquire(p,node) &&
parkAndCheckInterrupt())
throw newInterruptedException();
}
}finally{
if(failed)
cancelAcquire(node);
}
}
在我那篇AbstractQueuedSynchronizer源码解析讲过acquireQueued方法:
final booleanacquireQueued(finalNode node, intarg) {
booleanfailed =true;
try{
booleaninterrupted =false;
for(;;) {
finalNode p = node.predecessor();
if(p ==head&& tryAcquire(arg)) {
setHead(node);
p.next=null;// help GC
failed =false;
returninterrupted;
}
if(shouldParkAfterFailedAcquire(p,node) &&
parkAndCheckInterrupt())
interrupted =true;
}
}finally{
if(failed)
cancelAcquire(node);
}
}
仔细对比一下它们的区别,就只是当线程中断标志为true的时候,acquireQueued只是保存一下当前线程的状态(传递出去以免被重置了),而doAcquireInterruptibly直接就抛异常了。
那么中断的好处就是,当我们定义线程需要被中断的时候就直接异常,返回,不再继续执行下面的操作,节约了时间,对中断不了解的请看https://www.jianshu.com/p/5708ac61226b
ReentrantLock也就差不多了~写的有什么不对的地方欢迎指正。