统一概念
为了更好的理解,这里概念的定义进行一下统一;即根据线程状态来定义队列,WAITING和TIMED_WAITING状态下的线程进入的队列称为等待队列,BLOCKED状态下的线程进入的队列称为阻塞队列;其实条件队列和等待队列是一回事,同步队列和阻塞队列是一回事;
ReentrantLock独占锁加锁流程
尝试获取锁tryAcquire
1)公平模式下会先判断队列前边是否有等待的线程,若有,则走快速入队;
2)tryAcquire方法执行此逻辑,若state为0,则cas修改state字段状态由0到1,修改成功则设置exclusiveOwnerThread为当前线程;
3)同一个线程属于重入,state累加
没拿到锁就先执行一次快速入队addWaiter
addWaiter方法返回当前线程包装出来的node
1)tail不为null,则把当前线程包装成node节点以cas方式直接加在tail节点后
2)队列为空或cas加在tail节点失败,则进入enq方法执行完整入队逻辑
队列为空或快速入队失败则走完整入队enq
enq是个自旋方法,只有当入队成功了,才跳出自旋,返回当前节点的前置节点
1)若tail不为null,则cas设置当前节点为tail,不断自旋,直到成功
2)若tail为null,说明队列为空,说明第一个线程正拿着锁,且当前线程 有可能是第一个获取锁失败的线程(当前时刻可能存在一批获取锁失败的线程…),因为当前持锁的线程,它获取锁时,直接tryAcquire成功了,没有向阻塞队列中添加任何node,所以作为后继需要为它补一个节点,设置一个新node,head和tail都指向这个node,补完node之后,当前线程再次自旋不断尝试cas设置tail,直到成功。此时阻塞队列中的节点状态为初始默认值0,即0<——>0<——>0……0<——>0;
我觉得这个地方可能出现不公平竞争,即第一个获取锁失败的线程刚为持有锁的线程补全新节点,准备走cas入队逻辑时,刚好被别人抢先,所以doug lee眼中的公平锁应该指从排好队之后算起吧,入队之后,严格按照这个顺序获取锁,即为公平模式。
入队后可能会挂起当前线程,以及唤醒后相关的逻辑
1)acquireQueued是个自旋方法,结束自旋的条件是当前节点为head.next节点,且尝试获得锁成功。acquireQueued 返回true 表示挂起过程中线程被中断唤醒过,false 表示未被中断过。
2)判断当前节点不是head.next节点,或者是head.next但抢锁失败,则执行shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()。第一个方法用于删除取消状态节点,修改插入节点的前置节点状态为-1,自己则继续保持默认状态0,自己的状态从0到-1由其后继节点修改,最终阻塞队列中的所有节点状态变为-1<——>-1<——>…<——>0,因为状态为-1的节点才可以唤醒其next节点。这个方法最终都会返回true。节点的初始化状态均为0,取消节点状态为1;
3)在parkAndCheckInterrupt()方法中调用系统函数执行挂起,以及唤醒后返回是否是被中断唤醒,这里调用的是interrupted方法,返回是否被中断唤醒过,且会将中断标志位复位为false,唤醒后再次进入acquireQueued的自旋,重复以上逻辑。带中断的加锁中,方法doAcquireInterruptibly逻辑和不带中断加锁中的acquireQueued方法一样。(所以如果线程曾被中断过,则连续两次调用interrupted方法,第一次会返回true,第二次会返回false);
最后判断是否被中断唤醒
若是,则执行selfInterrupt方法,其中会执行interrupt方法置位当前线程的中断标记位为true(之所以要设置中断为true,是因为在parkAndCheckInterrupt方法中调用过一次interrupted方法,该方法用于检测是否有过中断,并且会复位中断标记位false)。这是不可中断的加锁流程,按照官方给的注释翻译过来应该叫忽略中断,留给业务处理;
可中断的加锁流程是,当被中断唤醒后,不会再次执行acquireQueued的自旋,而是直接抛异常InterruptedException(),进入cancelAcquire方法,该方法改变当前被中断的线程状态为取消状态,不让其参与锁竞争,而是直接唤醒