首先弄明白它的内部类之间的关系
Reentrantlock 内部类:
Sync ->AbstractQueuedSynchronizer
NonfairSync -> Sync
FairSync->Sync
AbstractQueuedSychronizer 内部类:Node
reentrantlock中实现公平锁和非公平锁
// 内部类 Sync的一个引用
private final Sync sync;
// 默认构造器为实现非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 根据传参选择公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
上述中出现的三个类的定义,具体内容接下来讲
// 实现同步机制的一个父类,继承了AbstractQueuedSynchronizer 下面有两个子类
abstract static class Sync extends AbstractQueuedSynchronizer
// 公平锁子类
static final class FairSync extends Sync
// 非公平锁子类
static final class NondairSync extends Sync
lock方法
实现独占锁的状况有三种
- 当前锁的状态为0 ,那么当前线程获取锁并将锁状态改为1,执行线程的业务逻辑
- 当前线程已经获得该锁,将锁状态+1,执行业务逻辑
- 锁状态不为0,当前线程也没有获得锁,该线程阻塞,等待获得锁后被唤醒,锁状态改为1,执行逻辑
public void lock() {
// 调用公平锁或非公平锁的lock方法
sync.lock();
}
lock实现的是sync中的抽象方法,该抽象方法在子类中的实现有两个,分别在公平类和非公平类中
非公平锁:
final void lock() {
// 如果cas尝试获取锁成功(将state锁状态从0设置为1)
if (compareAndSetState(0, 1))
//设置当前线程为独占锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 调用AbstractQueuedSynchronizer类中的acquire方法(该方法的具体实现)
acquire(1);
}
- AbstractQueuedSynchronizer 这个类中关于锁状态值的内容如下
// 定义锁状态值
private volatile int state;
// 获取锁状态方法
protected final int getState() {
return state;
}
// 更改锁状态值的方法
protected final void setState(int newState) {
state = newState;
}
- AbstractOwnableSynchronizer类中 setExclusiveOwnerThread方法
// 当前拥有独占锁的线程
private transient Thread exclusiveOwnerThread;
// 设置独占锁的线程为传入的线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
- acquire方法
// 调用的是AQS中的这个方法
public final void acquire(int arg) {
//如果获取失败返回false 会继续执行将当前线程链入队尾并挂起
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//中断自己
}
-
1 tryAcquire 方法(该方法有两个子类来实现,分别是FairSync,NonfairSync)
非公平类中方法如下
// NonfairSync 类中
protected final boolean tryAcquire(int acquires) {
// 其父类Sync中的方法
return nonfairTryAcquire(acquires);
}
// Sync类中
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取锁状态值
int c = getState();
if (c == 0) {
//如果当前没有线程在使用,直接使用cas尝试获取锁,新的线程可能抢占已经排队的线程的锁的使用权,
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);// 设置当前线程独占锁
return true;
}
}
//如果不为 0 则判断当前线程是不是独占锁的线程
else if (current == getExclusiveOwnerThread()) {
// 如果是将锁数量状态值+1(可重入锁来源)
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//请求即没有获取到锁,也不是当前独占锁的线程,返回false
return false;
}
3.2 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法 - 将当前线程链入队尾并挂起,等待被唤醒
3.2.1 AQS 中的 Node 类(这里是Node类实现的全部内容,有些用于共享锁,有些用于独占锁)
/**
* 同步等待队列(双向链表)中的节点
*/
static final class Node {
// 线程取消标识
static final int CANCELLED = 1;
/**
* 如果前驱节点的等待状态是SIGNAL,表示当前节点将来可以被唤醒,那么当前节点就可以安全的挂起了
* 否则,当前节点不能挂起
*/
static final int SIGNAL = -1;
// 线程正在等待条件
static final int CONDITION = -2;
// waitStatus值,指示下一个acquireShared应该无条件传播(共享锁使用)
static final int PROPAGATE = -3;
// 指示节点正在共享模式下等待的标记(共享锁使用)
static final Node SHARED = new Node();
// 一个标记:用于表明该节点正在独占锁模式下进行等待
static final Node EXCLUSIVE = null;
// 值就是前四个int(CANCELLED/SIGNAL/CONDITION/PROPAGATE),再加一个0
volatile int waitStatus;
//前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 节点中的线程
volatile Thread thread;
// 链接到等待条件的下一个节点,或特殊值 SHARED。(共享锁使用)
Node nextWaiter;
// 如果节点在共享模式下等待,返回 true (共享锁使用)
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回该节点前一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // 用于 addWaiter中
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Use by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
3.2.2 addWaiter(Node mode) 将node添加进等待队列
static final Node EXCLUSIVE = null;
addWaiter(Node.EXCLUSIVE)
/** 将node节点加入等待队列
* 快速入队,入队成功返回node
* 入队失败采用正常入队
* return 返回当前要插入的这个节点
*/
private Node addWaiter(Node mode) {
// 创建节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 快速入队,将尾节点赋给pred
Node pred = tail;
// 尾节点不为空
if (pred != null) {
// 将尾节点作为创造出来的节点的前一个节点,将node连接到尾节点后
node.prev = pred;
// 基于cas将node设置为尾节点,如果失败说明当前线程获取尾节点时有其他线程将尾节点替换过
/**
* 注意:假设有链表node1-->node2-->pred(当然是双链表,这里画成双链表才合适),
* 通过CAS将pred替换成了node节点,即当下的链表为node1-->node2-->node,
* 然后根据上边的"node.prev = pred"与下边的"pred.next = node"将pred插入到双链表中去,组成最终的链表如下:
* node1-->node2-->pred-->node
* 这样的话,实际上我们发现没有指定node2.next=pred与pred.prev=node2,这是为什么呢?
* 因为在之前这两句就早就执行好了,即node2.next和pred.prev这连个属性之前就设置好了
*/
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);// 正常入队
return node;
}
enq(node) 正常入队的方法
/**
* 正常入队
* @param node
* @return 之前的尾节点
*/
private Node enq(final Node node) {
for (;;) {//无限循环,一定要阻塞到入队成功为止
Node t = tail;//获取尾节点
if (t == null) { //如果尾节点为null,说明当前等待队列为空
/*
* 基于CAS将新节点(一个dummy节点)设置到头上head去,如果发现内存中的当前值不是null,则说明,在这个过程中,已经有其他线程设置过了。
* 当成功的将这个dummy节点设置到head节点上去时,我们又将这个head节点设置给了tail节点,即head与tail都是当前这个dummy节点,
* 之后有新节点入队的话,就插入到该dummy之后
*/
if (compareAndSetHead(new Node()))
tail = head;
} else {//这里的逻辑和快速入队一样
node.prev = t;// 将创造出来的结点连接到尾节点后面
if (compareAndSetTail(t, node)) {//尝试将node节点设为尾节点
t.next = node;//将node节点设为尾节点
return t;
}
}
}
}
3.2.3 acquireQueued()
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 一直阻塞,直到node的前驱结点p之前的所有节点都执行完毕,p成为head且node请求成功
for (;;) {
// 获取插入节点的前一个节点p
final Node p = node.predecessor();
/*
* 1、这个是跳出循环的唯一条件,除非抛异常
* 2、如果p == head && tryAcquire(arg)第一次循环就成功了,interrupted为false,不需要中断自己
* 如果p == head && tryAcquire(arg)第一次以后的循环中如果执行了挂起操作后才成功了,interrupted为true,就要中断自己
*/
if (p == head && tryAcquire(arg)) {
setHead(node);// 当前节点设置为头结点
p.next = null; // help GC
failed = false;
return interrupted;// 跳出循环
}
// 检测当前节点是否可以被安全挂起 ,挂起当前线程
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire(p,node) 检测当前节点是否可以被安全挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱节点的等待状态
int ws = pred.waitStatus;
// 如果前驱节点的等待状态是signal,表示当前节点将来可以被唤醒,那么就可以安全的挂起
if (ws == Node.SIGNAL)
return true;
// ws>0 (cancelled ==1 )前驱节点的线程被取消了,会将该节点之前的连续几个被取消的前驱节点从队列中剔除,返回false,不能挂起
// ws<0 && !=SIGNAL ,将当前节点的前驱节点的等待状态设为SIGNAL
if (ws > 0) {
do {
// pred=pred.prev
// node.prev=pred
// 被取消的节点去除,
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 找到 ws<0的节点和当前节点连接起来
pred.next = node;
} else {
/*
* 尝试将当前节点的前驱节点的等待状态设为SIGNAL
* 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢?
* (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作)
* 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false
* (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了)
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt()方法-挂起当前线程
// 挂起当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
cancelAcquire(node) 方法
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// 跳过取消的前节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext是要取消拼接的明显节点。 如果没有,以下情况将失败,在这种情况下,我们输掉了比赛,而另一个取消或发出信号,因此无需采取进一步措施。
Node predNext = pred.next;
// 用无条件写入代替cas,完成这步后其他节点可以跳过,在这以前不会受其他线程干扰
node.waitStatus = Node.CANCELLED;
// 如果这是尾节点,移开自己
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 如果后继者需要信号,请尝试设置pred的下一个链接
// 所以它会得到一个。 否则唤醒它以传播。
int ws;
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
}
}
公平锁
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取锁状态
int c = getState();
// 如果当前没有线程获取锁,
if (c == 0) {
// !hasQueuedPredecessors()保证线程都按顺序使用锁
// 判断当前线程是否为等待队列中的头结点
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); //
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
判断是否为头结点
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
总结:公平锁与非公平锁对比
- FairSync:lock()少了插队部分(即少了CAS尝试将state从0设为1,进而获得锁的过程)
- FairSync:tryAcquire(int acquires)多了需要判断当前线程是否在等待队列首部的逻辑(实际上就是少了再次插队的过程,但是CAS获取还是有的)。
最后说一句,
- ReentrantLock是基于AbstractQueuedSynchronizer实现的,AbstractQueuedSynchronizer可以实现独占锁也可以实现共享锁,ReentrantLock只是使用了其中的独占锁模式
unlock
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 如果释放锁成功
if (tryRelease(arg)) {
Node h = head;
// 如果头节点不为空,而且节点状态不为0时
if (h != null && h.waitStatus != 0)
// 唤醒线程
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease方法在Sync类中
protected final boolean tryRelease(int releases) {
// 将锁状态值减去当前需要释放的值
int c = getState() - releases;
// 如果当前线程不是独占锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果当前锁状态值为0
if (c == 0) {
free = true;
// 设置当前独占锁的线程为null
setExclusiveOwnerThread(null);
}
// 设置锁状态为修改后的值
setState(c);
return free;
}
unparkSuccessor(Node node)方法
private void unparkSuccessor(Node node) {
// 如果状态小于0 ,尝试更改状态为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}