最详细的reentrantlock原理

目录

非公平锁实现原理

加锁流程

可重入原理

可打断原理(默认不可打断)

 可打断模式

非公平锁和公平锁的比较

await流程

 signal方法


非公平锁实现原理

加锁流程

最详细的reentrantlock原理_第1张图片

首先,通过CAS将状态由0改为1,如果成功了就将owner线程改为当前线程

最详细的reentrantlock原理_第2张图片 如果CAS失败,则说明有竞争,进入else分支,进入acquire()方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

我们发现会先调用tryAcquire方法,因为现在是非公平锁,又会调用nonfairTryAcquire方法

最详细的reentrantlock原理_第3张图片 该方法会先检查state是不是0,结合上文,如果竞争者此时unlock,这时就会直接返回true,但此时是有竞争,所以不会走这个if分支,else if判断竞争者是不是自己,如果两个条件均不满足,返回false 最详细的reentrantlock原理_第4张图片

即!tryAcquire(arg)的结果为true,进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,

首先执行addWaiter,这个方法会尝试创建一个节点对象,并且与当前线程关联,进入等待队列中,等待队列是一个有头尾节点的双向链表

如果是第一次创建,会创建两个Node,其中第一个Node称为Dummy(哑元)或者哨兵,用来占位,并不关联线程

最详细的reentrantlock原理_第5张图片

 随后进入acquireQueued逻辑

最详细的reentrantlock原理_第6张图片

node是和当前Thread相关联的节点,首先node.predecessor()方法获取当前节点的前驱节点,

接着检查前驱节点是不是头结点,即自己紧挨着head,排第二位,那么会再次尝试tryAcquire获取锁,如果很幸运,之前占据锁的线程释放了锁,那么当前线程就可以获得锁,退出。

失败后进入下一个if判断,进入 shouldParkAfterFailedAcquire 逻辑,将前驱node,即head的waitStatus改为-1,-1的作用是有责任唤醒他后继节点的线程,因为当前的节点,已经尝试过很多次都没有获得锁了,应该进入阻塞状态了,而他的前驱节点就用来将这个线程唤醒,完成工作之后,这个方法返回false。

最详细的reentrantlock原理_第7张图片 然后再次进入这个for循环,再次执行tryAcquire,第一个if条件还是不满足,再次进入shouldParkAfterFailedAcquire 逻辑,但此时前驱node节点已经是-1了,这次返回true,进入parkAndCheckInterrupt逻辑最详细的reentrantlock原理_第8张图片

 就是调用park方法,阻塞住当前线程

最详细的reentrantlock原理_第9张图片

 再有多个线程经历上述的竞争过程,变成下边这个样子

最详细的reentrantlock原理_第10张图片

此时,Thread - 0 释放锁,进入tryRelease流程,如果成功

  • 设置exclusiveOwnerThread为null
  • state = 0

最详细的reentrantlock原理_第11张图片

即Thread - 0调用unlock()方法

最详细的reentrantlock原理_第12张图片

 而在其内部调用了release方法,而在release方法的内部又先调用了tryRelease方法, tryRelease方法将state状态设置为0,并返回true,且去检查head是不是空的,且是不是他的waitStatus!=0(为-1就要去通知他的后继节点),此时head不为空,且waitStatus为-1,进入unparkSuccessor流程

最详细的reentrantlock原理_第13张图片

unparkSuccessor会将head的后继节点unpark,即恢复后继节点线程的运行

最详细的reentrantlock原理_第14张图片

找到队列中里head最近的一个Node,unpark恢复其运行,本例中为Thread - 1,我们观察Thread - 1是什么时候进入被park的,是在第二个if中的第二个方法中被阻塞住的,现在Thread1被唤醒,继续执行,因为没有被打断过,第二个if方法返回false,再次进入for循环

最详细的reentrantlock原理_第15张图片

如果加锁成功(没有竞争) ,会设置

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head指向刚刚Thread - 1所在的Node,该Node清空Thread
  • 原本的head因为从链表断开,而可被垃圾回收

最详细的reentrantlock原理_第16张图片

如果这时候有其他线程来竞争(非公平),例如此时有Thead - 4来了

最详细的reentrantlock原理_第17张图片

即在tryAcquire的过程中被Thread-4抢了先

  • Thread-4被设置为exclusiveOwnerThread,state = 1
  • Thread - 1再次进入acquireQueued流程,获取锁失败, 重新进入park阻塞

可重入原理

最详细的reentrantlock原理_第18张图片

观察else if语句,当不是第一次加锁的时候,且 current == getExclusiveOwnerThread() ,说明当前占据线程的是自己,发生锁重入,就是将state 变为 state+1(入参acquires的值为1),表示重入的次数。

最详细的reentrantlock原理_第19张图片

解锁的时候,releases也为1,c就是状态-1后的数,就是先让锁重入的计数-1,如果为0,说明当前的线程所有的锁都释放了,如果不为0,说明当前线程还有锁未释放,当且仅当state为0,才将free变量置位true,返回true,否则都是state-1,然后返回false,即解锁失败。

可打断原理(默认不可打断)

默认是不可打断的,当线程无法获取锁的时候,就会进入acquireQueued方法,他会进入第二个if条件中的parkAndCheckInterrupt的park方法阻塞住,最详细的reentrantlock原理_第20张图片

其他线程可以通过interrupt打断他,打断他的时候interrupted就被置位true,interrupted方法,不但会返回打断标记,同时还会清除打断状态,下次park还会park住他

最详细的reentrantlock原理_第21张图片

因为方法返回值为true,所以进入这个if块,interrupted这个变量本来是false,现在被记录为true,但是没有做更多的额外处理,再次进入下次循环,然后继续被阻塞住。

什么时候会用到这个变量,他获取到锁的时候,会将这个变量返回回去。

最详细的reentrantlock原理_第22张图片 就会执行selfInterrupt方法,重新产生一次中断

 最详细的reentrantlock原理_第23张图片

 可打断模式

需要使用acquireInterruptibly()方法

最详细的reentrantlock原理_第24张图片

如果没有获取到锁,进入doAcquireInterruptibly,其他方法和acquireQueued相同,但是被打断后进入if块后不是将状态置位true,而是直接抛出异常

最详细的reentrantlock原理_第25张图片

非公平锁和公平锁的比较

非公平锁,其他线程只要看见状态为0,不会去检查等待队列的状态,直接就使用CAS去尝试获取锁了 

最详细的reentrantlock原理_第26张图片

 公平锁会先去执行 hasQueuePredecessors 方法,会先去查看队列中有没有线程在队列中等待,有则进入不了这个分支,没有抢的资格。

最详细的reentrantlock原理_第27张图片

await流程

每个条件变量其实就是对应着一个等待队列,其实现类就是ConditionObject

最详细的reentrantlock原理_第28张图片

 刚开始Thread - 0持有锁,调用await,进入ConditionObject的addConditionWaiter流程,创建新的Node状态为 -2 ,关联Thread - 0 ,加入等待队列尾部,随后调用fullyRelease()方法,释放所有的锁,fully用的很讲究,因为考虑到了锁重入,所以可能要释放多把锁,fullyRelease中还会调用release方法唤醒后继节点的线程

随后,自己调用park方法,阻塞住自己

最详细的reentrantlock原理_第29张图片

 最详细的reentrantlock原理_第30张图片

 signal方法

最详细的reentrantlock原理_第31张图片

先检查当前线程是否是锁的持有者,如果不是,直接抛出异常

如果是,就去找等待队列的队列头元素,如果不为空,就调用doSignal方法 

最详细的reentrantlock原理_第32张图片

先判断是不是队列中就他一个节点,如果是就直接为null了 

transferForSignal方法会将Node节点将等待队列转移到竞争锁的队列中,并且将状态变为0,同时要将原来的队尾Node的状态改为-1(他有责任唤醒它的后继节点),转移成功就返回true,!取反,退出while循环,但如果转移失败(可能这个节点对应的线程被打断,不再去竞争锁等情况),就去找下一个节点去唤醒。

最详细的reentrantlock原理_第33张图片

你可能感兴趣的:(多线程,开发语言,后端,java)