这篇文章(包括后边两篇)是六月份写的,写完后由于工作较忙,草稿一致没时间修改,今天偶尔翻到,担心哪天弄丢了,于是先以草稿的形式发布,内容有瑕疵,等以后有时间再修复。
首先来看类的继承关系,ReetrantLock继承的Lock接口,其内部还存在三个内部类,Sync,NonfairSync和FairSync 其中NonfairSync和FairSync都是继承自Sync。
这幅图包含了各类中的方法,其中Lock接口有六个方法,ReentrantLock实现了这个接口,
方法从上至下看其名称大概知晓其意思。分别是
看这些方法的实现,全部跟一个private final Sync sync;的成员变量有关。换句话说,lock接口只规定了语义,而真正实现都是使用Sync对象的方法。
既然这样我们看Sync类的继承关系。
Sync继承了AbstractQueuedSynchronizer抽象类,AbstractQueuedSynchronizer类又有两个内部类,一个是Node另一个是ConditionObject.
概览一下sync中的方法,lock是个抽象方法留给子类去实现,剩下的也基本见名知意,随意翻开几个方法查看,基本都有调用父类的方法进行实现,因此可以推断出,父类AbstractQueuedSynchronizer可能是非常核心的一个类。
Sync有两个子类,NofairSync和FairSync,公平和非公平的同步器,猜想肯定跟公平锁和非公平锁有关。
当我们在代码中写到
ReentrantLock lock=new ReentrantLock();实际上是调用了下边这个构造器,里边的同步器是非公平的,这也就是常说的默认情况ReentrantLock使用的是非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
reteenlock类
sync类
AbstractQueuedSynchronizer类
NonfairSync类
看一个常用方法Lock();
public void lock() {
sync.lock();
}
主要就是调用了sync对象的lock方法,而默认使用的是非公平锁即NoFairSync类的实现
final void lock() {
if (compareAndSetState(0, 1))//比较设置值,设置成功即为获得锁
setExclusiveOwnerThread(Thread.currentThread());//如果获得锁把当前线程设置为持有线程
else
acquire(1);//如果没获取到调用获取方法
}
尝试获取锁,就是使用乐观思想把值从0设置到1,如果设置成功证明当前线程获取了锁,那么把当前线程设置为持有锁的线程,如果没获取到则执行获取锁的操作
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
这个方法就是原子操作把0设置为1
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//尝试获取锁失败
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//排队并获取成功,但是线程已经被打断,则调用本线程进行中断,如果线程没有被中断则成功获取锁
selfInterrupt();//打断获取
}
尝试获取,非公平锁在排队之前还会抢先进行一步获取,如果成功就拿到了锁,如果失败去排队,如果排队也失败,打断当前线程。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);//调用父类方法
}
此方法是框架类常使用的一种方式,父类定义抽象,子类进行实现。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取当前状态
if (c == 0) {//0代表没有线程获取锁
if (compareAndSetState(0, acquires)) {//尝试获取锁
setExclusiveOwnerThread(current);//获取成功把当前线程设置为占有线程
return true;//返回结束
}
}
else if (current == getExclusiveOwnerThread()) {//如果已经有线程持有锁,胖多是否为自己持有,这是重入锁的概念
int nextc = c + acquires;//如果自己持有锁数加一
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);//更新持有数
return true;//返回结束
}
return false;//没获取到 返回
}
这个方法就是非公平锁抢先尝试获取,如果没有线程占有,直接获取,并设置当前线程为持有线程,如果已经有线程持有,判断是否为自己,如果是自己,自己重入获取锁,把锁的数量加一,如果没获取到返回失败标志。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//把当前线程包装成node
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//记住当前尾节点
if (pred != null) {//如果尾节点
node.prev = pred;//把当前节点的前置节点设置为之前的尾节点
if (compareAndSetTail(pred, node)) {//原子操作设置当前节点为尾节点
pred.next = node;//把之前尾节点的下一节点设置为当前节点
return node;//这里返回就不会往下走了
}
}
enq(node);
return node;
}
代码不复杂也不难,就是包装当前线程为一个node对象,并返回当前node引用
private Node enq(final Node node) {
for (;;) {
Node t = tail;//获取当前尾节点
if (t == null) { // 如果尾节点为空证明没有排队的
if (compareAndSetHead(new Node()))//如果新建一个头节点成功
tail = head;//把头尾指向同一节点
} else {//尾节点不为空,证明有排队的
node.prev = t;//当前节点的前置节点设置为目前的尾节点
if (compareAndSetTail(t, node)) {//比较设值,设置当前节点为尾节点
t.next = node;//设置成功之前尾节点的下一节点设置为当前节点
return t;//返回
}
}
}
}
分析这个方法,在一个for循环里并且不走else分支就会始终循环,直到走else分支,
假设进来时tail为null,那么走if这是进行初始化,并设置值,走完if分支之后,tail已经有值,第二次循环走else分支,把当前节点加入到排队队列。重点就是if块是初始化排队队列的关键点。
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
新建个头节点,headOffset在静态初始化块中记录了head属性的偏移量
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//获取当前节点的前置节点
if (p == head && tryAcquire(arg)) {//如果前置节点事head节点,那么尝试获取锁
setHead(node);//获取成功把当前节点设置为头结点
p.next = null; // help GC//断开当前节点前置节点的下一节点指向
failed = false;//失败标志设置为false
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&//如果前置节点不是head或者获取锁失败,先执行shouldParkAfterFailedAcquire(p, node)方法,如果这个方法返回true执行parkAndCheckInterrupt()方法,否则继续循环
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//如果等待过程发线程抛出异常,则取消获取
cancelAcquire(node);//取消获取
}
}
代码中有个无限for循环,只有走if才会结束,开始获取当前节点的前置节点用p指向,如果p是head,尝试获取锁,为什么这么写呢?看图可知,当初始化时sync对象head指向的是一个new出来的没有包装线程的node对象,这个对象是不会去获取锁的,因此这时调用tryAcquire(arg)如果没有其他线程竞争,是可以获取锁的,如果成功获取锁,就将head指向当前节点,同时将前置节点的next设置为null,便于垃圾回收。如果前置节点不是head或者获取锁失败(非公平锁),进入shouldParkAfterFailedAcquire(p, node)方法,如果此方法返回true再执行parkAndCheckInterrupt()方法。
static final int CANCELLED = 1;//因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。
static final int SIGNAL = -1;//表示这个结点的继任结点被阻塞了,到时需要通知它
static final int CONDITION = -2;//表示这个结点在条件队列中,因为等待某个条件而被阻塞
static final int PROPAGATE = -3;//使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获取前置节点的状态
if (ws == Node.SIGNAL)//如果等于 Node.SIGNAL直接返回true
return true;
if (ws > 0) {//如果大于0,循环检查前置节点并提出
do {
node.prev = pred = pred.prev;//把当前节点的前置节点指向前置节点的前置节点
} while (pred.waitStatus > 0);//检查新的前置节点的状态
pred.next = node;//将最终的新的前置节点的next指向当前节点(彻底断开cancle状态节点的排队等待,剔除它们)
} else {//等于-2或者-3情况
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将前置节点的状态更新为singnal
}
return false;
}
分析这个方法,每次进来只能走一个分支,
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞线程
return Thread.interrupted();//返回线程是否被interrupted
}
如果期间发生异常则执行cancelAcquire()方法
private void cancelAcquire(Node node) {
// 不存在直接返回
if (node == null)
return;
node.thread = null;//将node对象中的thread置为null
Node pred = node.prev;//获取当前节点的前置节点
while (pred.waitStatus > 0)//循环判断前置节点的状态为取消,直到前置节点的状态不再是取消为止
node.prev = pred = pred.prev;//将当前节点与前置节点断开,并将当前节点的前置节点指向原有前置节点的前置节点
Node predNext = pred.next;//获取前置节点的后置节点
node.waitStatus = Node.CANCELLED;//将当前节点的状态设置为取消
if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点事尾节点,把当前节点的前置节点设置为尾节点
compareAndSetNext(pred, predNext, null);
} else {//如果当前节点不是尾节点,
int ws;
//前置节点不是头节点,并且(满足前置节点的状态为等待,或者满足前置节点的状态小于0并且将前置节点的状态成功的设置为等待状态),并且还要满足前置节点的thread对象不为null
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;//记录当前节点的后置节点
if (next != null && next.waitStatus <= 0)//后置节点存在且状态不为取消
compareAndSetNext(pred, predNext, next);//将前置节点的下一节点设置为当前节点的下一节点,即断开当前节点的连接
} else {
unparkSuccessor(node);//唤醒后继等待线程
}
node.next = node; // help GC
}
}