ReentrantLock源码解析

看这部分的前提是大家已经看过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也就差不多了~写的有什么不对的地方欢迎指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容