目录
非公平锁实现原理
加锁流程
可重入原理
可打断原理(默认不可打断)
可打断模式
非公平锁和公平锁的比较
await流程
signal方法
首先,通过CAS将状态由0改为1,如果成功了就将owner线程改为当前线程
如果CAS失败,则说明有竞争,进入else分支,进入acquire()方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们发现会先调用tryAcquire方法,因为现在是非公平锁,又会调用nonfairTryAcquire方法
该方法会先检查state是不是0,结合上文,如果竞争者此时unlock,这时就会直接返回true,但此时是有竞争,所以不会走这个if分支,else if判断竞争者是不是自己,如果两个条件均不满足,返回false
即!tryAcquire(arg)的结果为true,进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,
首先执行addWaiter,这个方法会尝试创建一个节点对象,并且与当前线程关联,进入等待队列中,等待队列是一个有头尾节点的双向链表
如果是第一次创建,会创建两个Node,其中第一个Node称为Dummy(哑元)或者哨兵,用来占位,并不关联线程
随后进入acquireQueued逻辑
node是和当前Thread相关联的节点,首先node.predecessor()方法获取当前节点的前驱节点,
接着检查前驱节点是不是头结点,即自己紧挨着head,排第二位,那么会再次尝试tryAcquire获取锁,如果很幸运,之前占据锁的线程释放了锁,那么当前线程就可以获得锁,退出。
失败后进入下一个if判断,进入 shouldParkAfterFailedAcquire 逻辑,将前驱node,即head的waitStatus改为-1,-1的作用是有责任唤醒他后继节点的线程,因为当前的节点,已经尝试过很多次都没有获得锁了,应该进入阻塞状态了,而他的前驱节点就用来将这个线程唤醒,完成工作之后,这个方法返回false。
然后再次进入这个for循环,再次执行tryAcquire,第一个if条件还是不满足,再次进入shouldParkAfterFailedAcquire 逻辑,但此时前驱node节点已经是-1了,这次返回true,进入parkAndCheckInterrupt逻辑
就是调用park方法,阻塞住当前线程
再有多个线程经历上述的竞争过程,变成下边这个样子
此时,Thread - 0 释放锁,进入tryRelease流程,如果成功
即Thread - 0调用unlock()方法
而在其内部调用了release方法,而在release方法的内部又先调用了tryRelease方法, tryRelease方法将state状态设置为0,并返回true,且去检查head是不是空的,且是不是他的waitStatus!=0(为-1就要去通知他的后继节点),此时head不为空,且waitStatus为-1,进入unparkSuccessor流程
unparkSuccessor会将head的后继节点unpark,即恢复后继节点线程的运行
找到队列中里head最近的一个Node,unpark恢复其运行,本例中为Thread - 1,我们观察Thread - 1是什么时候进入被park的,是在第二个if中的第二个方法中被阻塞住的,现在Thread1被唤醒,继续执行,因为没有被打断过,第二个if方法返回false,再次进入for循环
如果加锁成功(没有竞争) ,会设置
如果这时候有其他线程来竞争(非公平),例如此时有Thead - 4来了
即在tryAcquire的过程中被Thread-4抢了先
观察else if语句,当不是第一次加锁的时候,且 current == getExclusiveOwnerThread() ,说明当前占据线程的是自己,发生锁重入,就是将state 变为 state+1(入参acquires的值为1),表示重入的次数。
解锁的时候,releases也为1,c就是状态-1后的数,就是先让锁重入的计数-1,如果为0,说明当前的线程所有的锁都释放了,如果不为0,说明当前线程还有锁未释放,当且仅当state为0,才将free变量置位true,返回true,否则都是state-1,然后返回false,即解锁失败。
默认是不可打断的,当线程无法获取锁的时候,就会进入acquireQueued方法,他会进入第二个if条件中的parkAndCheckInterrupt的park方法阻塞住,
其他线程可以通过interrupt打断他,打断他的时候interrupted就被置位true,interrupted方法,不但会返回打断标记,同时还会清除打断状态,下次park还会park住他
因为方法返回值为true,所以进入这个if块,interrupted这个变量本来是false,现在被记录为true,但是没有做更多的额外处理,再次进入下次循环,然后继续被阻塞住。
什么时候会用到这个变量,他获取到锁的时候,会将这个变量返回回去。
需要使用acquireInterruptibly()方法
如果没有获取到锁,进入doAcquireInterruptibly,其他方法和acquireQueued相同,但是被打断后进入if块后不是将状态置位true,而是直接抛出异常
非公平锁,其他线程只要看见状态为0,不会去检查等待队列的状态,直接就使用CAS去尝试获取锁了
公平锁会先去执行 hasQueuePredecessors 方法,会先去查看队列中有没有线程在队列中等待,有则进入不了这个分支,没有抢的资格。
每个条件变量其实就是对应着一个等待队列,其实现类就是ConditionObject
刚开始Thread - 0持有锁,调用await,进入ConditionObject的addConditionWaiter流程,创建新的Node状态为 -2 ,关联Thread - 0 ,加入等待队列尾部,随后调用fullyRelease()方法,释放所有的锁,fully用的很讲究,因为考虑到了锁重入,所以可能要释放多把锁,fullyRelease中还会调用release方法唤醒后继节点的线程
随后,自己调用park方法,阻塞住自己
先检查当前线程是否是锁的持有者,如果不是,直接抛出异常
如果是,就去找等待队列的队列头元素,如果不为空,就调用doSignal方法
先判断是不是队列中就他一个节点,如果是就直接为null了
transferForSignal方法会将Node节点将等待队列转移到竞争锁的队列中,并且将状态变为0,同时要将原来的队尾Node的状态改为-1(他有责任唤醒它的后继节点),转移成功就返回true,!取反,退出while循环,但如果转移失败(可能这个节点对应的线程被打断,不再去竞争锁等情况),就去找下一个节点去唤醒。