Reentrantlock
AQS
内部虚拟类Sync 继承了 AQS(AbstractQueuedSynchronizer )
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
······
static final class Node {
······
volatile Node prev;
volatile Node next;
volatile Thread thread;
······
}
//队列头
private transient volatile Node head;
//队列头
private transient volatile Node tail;
//同步状态标识
private volatile int state;
······
}
Reentrantlock实现了两种锁分别是公平锁(FairSync)和非公平锁(NonfairSync)
FairSync
首先来分析下公平锁的原理,以下是FairSync实现源码
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
调用lock()方法时会调用acquire方法
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
按其英文注释可知,此方法是以独占模式获取锁,忽略中断。实现通过至少调用一次tryAcquire()方法,结果返回成功。若返回失败,线程可能会排队反复阻塞和解除阻塞,调用tryAcquire()方法直到成功。
那么就来看下tryAcquire方法,以下是FairSync的tryAcquire方法的实现
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//当第一次进行lock时c==0
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;
}
Deme例子代码
写一个Demo进行测试与分析
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.System.out;
public class ReentrantLockDemo1 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock(true);
Thread thread1 = new Thread(()-> {
out.println("thread1 第一次进行加锁操作");
reentrantLock.lock();
out.println("thread1 第一次加锁成功");
out.println("thread1 第二次次进行加锁操作");
reentrantLock.lock();
try {
out.println("thread1 第二次加锁成功");
TimeUnit.SECONDS.sleep(2);//设置占有锁2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("thread1 释放锁资源");
reentrantLock.unlock();
reentrantLock.unlock();
});
thread1.start();
//确保线程先后顺序
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(()->{
out.println("thread2 进行加锁操作");
reentrantLock.lock();//线程2进行锁占有操作
out.println("thread2 加锁成功");
reentrantLock.unlock();
out.println("thread2 释放锁资源");
});
thread2.start();
//确保线程先后顺序
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread3 = new Thread(()->{
out.println("thread3 进行加锁操作");
reentrantLock.lock();
out.println("thread3 加锁成功");
reentrantLock.unlock();
out.println("thread3 释放锁资源");
});
thread3.start();
}
}
Demo输出结果
以下为输出结果
thread1 第一次进行加锁操作
thread1 第一次加锁成功
thread2 进行加锁操作
thread3 进行加锁操作
thread1 第二次次进行加锁操作
thread1 第二次加锁成功
thread1 释放锁资源
thread2 加锁成功
thread2 释放锁资源
thread3 加锁成功
thread3 释放锁资源
lock流程分析
以下是个人对这个Demo的ReentrantLock类从源码的角度进行加锁过程的分析
- 1、首先此时线程thread1 第一次进行lock时c==0
会进入 if (c == 0) 里面 - 2、首先调用hasQueuedPredecessors(),判断队列里是否有线程在等待
此时的 tail和head都为null,则t== h== null
所以 h != t 为false ,则hasQueuedPredecessors()返回==false
那么!hasQueuedPredecessors() ==true 即没有线程在队列中
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());
}
- 3、接着 compareAndSetState(0, acquires)) 进行加锁原子操作,会将status设置为1,成功后返回true
- 4、进入 setExclusiveOwnerThread(current)方法,设置当前线程thread1占用锁资源,此时thread1会打印 “thread1 第一次加锁成功”
- 5、thread1 在加锁成功后,会接着执行第二次加锁操作,再次lock()->acquire(1)->tryAcquire(1)。
此时的c==1所以会执行 else if (current == getExclusiveOwnerThread()) 为true,则执行else if代码块
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
nextc ==c+acquires=2, setState(nextc);则status会变成2,并返回true,表明加锁成功 ,会打印"thread1 第二次加锁成功"
- 6、然后thread2开始执行加锁操作,执行lock()->acquire(1)->tryacquire(1),
tryacquire(1)会返回false - 7、所以!tryAcquire(arg)为true,接着会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
8、 分析下acquireQueued(addWaiter(Node.EXCLUSIVE), arg)操作中的
addWaiter(Node.EXCLUSIVE) 方法执行
此时node保存当前线程,此时即为thread2;
然后Node pred = tail;将当前线程节点的前一个节点指向队列尾部,此时队列为空队尾tail自然为空,所以pred为null;
所以不进入if代码块,直接执行enq(node),将thread2线程节点入队列
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//node保存当前线程,此时即为thread2
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//将当前线程节点的前一个节点指向队列尾部
if (pred != null) {//此时队列为空队尾tail自然为空,所以pred为null
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//执行入队操作
return node;
}
- 9、将thread2线程节点入队列,enq(node)
9.1 首先判断队尾是否为空,此时队列为空,队尾是null,则执行if代码块。
9.2 然后compareAndSetHead(new Node())方法将head初始化为new Nodw()并且返回true。
9.3 接着tail = head;将队尾也进行初始化。
9.4 进行for循环第二遍执行,此时因为队头队尾都初始化了,所了进行else代码块
node.prev = t;将thread2节点的prev即一个节点设置为队尾
9.5 然后compareAndSetTail(t, node)将thread2节点设置为队尾,设置成功后返回true。
9.6 设置原来的队尾t的下一结点为thread2
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
9.7 那么此时addWaiter(Node mode)操作完成,队列中有两个节点,如下图所示
- 10、此时会返回thread2的node并调用acquire方法中的 acquireQueued(addWaiter(node_thread2), 1)方法
英文注释翻译:获取已处于独占不可中断模式的线程
队列中。用于条件等待方法以及获取。
分析下此时的执行流程
10.1 此处node为thread2的线程节点,p=pred,此时的p为无线程的节点
10.2 此时的p==head为true,那么会执行tryAcquire(1),thread1已占有锁资源所以tryAcquire(1)为false,则不进入此If代码块,会进入下一if判断
10.3 那么接着会执行shouldParkAfterFailedAcquire(p, node)方法
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//此处node为thread2的线程节点,p=pred,此时的p为无线程的节点
final Node p = node.predecessor();
//此时的p==head为true,那么会执行tryAcquire(1)
//thread1已占有锁资源所以tryAcquire(1)为false
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//那么会执行此处shouldParkAfterFailedAcquire方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 11、所以,接着来看shouldParkAfterFailedAcquire方法的源码
源码英文注释翻译:
检查和更新未能获取锁资源的节点的状态。
如果线程应该被阻塞,则返回true。这是控制所有获取资源操作的循环主要信号,需要满足pred == node.prev。
11.1 int ws = pred.waitStatus;//ws为head的ws,初始为0,所以会执行else代码块。
11.2 调用 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);方法,即compareAndSetWaitStatus(head, 0,-1)将waitStatus 从0置为1。
11.3 最后返回false,则acquireQueued方法中的 for (;;)循环会第二次行,又是相同情况,所以同样会再次调用shouldParkAfterFailedAcquireshouldParkAfterFailedAcquire(p, node)方法。
11.4 此时的 int ws = pred.waitStatus//已经被设置为1 所以 ws为-1。
11.5 所以 if (ws ==Node.SIGNAL)条件成立,进入代码块执行return true。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 12、shouldParkAfterFailedAcquire返回了true代表此线程应该被阻塞
会回到acquireQueued方法中调用parkAndCheckInterrupt()方法。
分析下parkAndCheckInterrupt()方法的源码。
英文注释翻译: 用于停放的方便的方法,然后检查是否中断。
意思即是用于阻塞线程的方法,并且会判断是否中断阻塞。
此方法调用了LockSupport.park(this);里面的this代表tread2,因此thread2会阻塞在此处,等待被唤醒
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 13 在thread2被阻塞之后,此时主线程执行thread3.start()->acuquire(1)->tryAcquire(1)->!tryAcquire(1)&&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
此时tryAcquire(1)同样返回false,则 !tryAcquire(1)为true
所以会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))中的addWaiter(Node.EXCLUSIVE),将thread3保存到新的node节点中,并加入等待队列。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//此时tryAcquire(1)同样返回false
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
此时AQS队列状态如下图
然后执行acquireQueued(node_thread3)
同样会执行shouldParkAfterFailedAcquire(p, node) 会把node_thread2节点的waitStatus设置为Node.SIGNAL即-1。
接着执行parkAndCheckInterrupt()
待续
以上是关于加锁(lock)与线程入队操作的解析,下一篇会继续解析解锁(unlock)与线程出队列相关的操作,以下链接:
Java AQS学习 之 ReentrantLock源码解析(二)