并发学习记录19:ReentrantLock和AQS
概述
AQS全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
特点是:
用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
state的访问方式有三种,如下:
getState方法是获取state状态
setState方法是设置state状态
compareAndSetState是利用cas机制设置state状态
独占模式是只有一个线程能够访问资源,而共享模式指的是允许多个线程访问资源
AQS提供了基于先进先出的等待队列,类似于Monitor的EntryList
AQS还提供了条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor中的WaitSet
子类主要需要实现这样的一些方法:
//尝试以独占方式去获得锁
tryAcquire
//尝试去释放锁
tryRelease
//尝试以共享模式去获取锁
tryAcquireShared
//尝试以共享模式去释放锁
tryReleaseShared
//判断当前线程是否是正在独占资源,conditionObject用到的时候就需要去实现它
//Returns:true if synchronization is held exclusively; false otherwise
//This method is invoked internally only within AbstractQueuedSynchronizer.ConditionObject methods
isHeldExclusively
AQS的节点状态:
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
一共有以上四种,节点状态用waitStatus记录:
*CANCELLED :表示这个节点已经被取消调度,当timeout或被中断时会出发变成这个状态,进入这个状态的节点不会再变化。
*SIGNAL :表示后继的节点需要被unparking。后继节点入队时,前驱节点会更新成为signal
- CONDITION :表示这个节点上的线程等待在condition上,当其他线程调用了condition的signal方法,condition状态的节点将从等待队列转移到同步队列中,等待获取同步锁
- PROPAGATE :共享模式下,前驱节点不止会唤醒后继节点,可能也会唤醒其他节点。
acquire(int)
/**
* 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();
}
tryAcuqire(int)
这个方法尝试去获取独占资源,更加具体的获取方式就要由继承AQS的子类去实现。
/**
* Attempts to acquire in exclusive mode. This method should query
* if the state of the object permits it to be acquired in the
* exclusive mode, and if so to acquire it.
*
* <p>This method is always invoked by the thread performing
* acquire. If this method reports failure, the acquire method
* may queue the thread, if it is not already queued, until it is
* signalled by a release from some other thread. This can be used
* to implement method {@link Lock#tryLock()}.
*
* <p>The default
* implementation throws {@link UnsupportedOperationException}.
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return {@code true} if successful. Upon success, this object has
* been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
acquireQueued就是把当前线程放入等待队列,如果等待队列中只有当前线程在等待,当前线程就尝试去获取资源,如果等待队列有很多线程在等待,当前线程就进入阻塞状态。
/**
* 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 (;;) {
//p是node的前驱
final Node p = node.predecessor();
//如果node的前驱是head节点,由于head节点内部不含线程,所以此时 //node就是等待队列的头节点,所以可以尝试去尝试获取资源
if (p == head && tryAcquire(arg)) {
//这里node成功获取了资源
//node变成head
setHead(node);
//node的前驱彻底出队
p.next = null; // help GC
//成功获得资源
failed = false;
//返回等待过程中是否被打断
return interrupted;
}
//能执行到这里,说明node的前驱非头结点,或者node自己在竞争资源的时 //候失败了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
检查并更新当前这个没有获得资源的节点的状态,如果当前这个节点中的线程需要阻塞就返回true。
如果返回true,就说明前驱节点状态正常,当前线程需要被挂起,然后就park当前线程等待被唤醒
如果返回false,不挂起线程,而是重新进入自旋锁,由于走了一遍shouldParkAfterFailedAcquire的do-while循环,所以此时本线程的前驱已经是signal状态的前驱
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的状态
int ws = pred.waitStatus;
//如果前驱节点是状态为SIGNAL,说明node节点是应该被唤醒的,但是又没被唤醒, //所以node节点中的线程应该是需要被挂起的
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) {
//如果前驱节点大于0,那么就说明前驱节点已经被cancel,而等待队列中节点的唤醒
//都是依靠前驱节点来唤醒的,前驱节点状态为负数值才能唤醒后续节点,所以就得往
//前遍历,直到找到前驱节点状态为负数的节点,然后把node的前驱指向这个找到的新 //前驱
/*
* 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.
*/
//进入这个分支说明前驱节点的waitStatus非cancel和signal,需要改成
//signal来唤醒后续节点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
addWaiter(Node)
这个方法就是将当前的线程放入等待队列的队尾,最后返回当前线程所在的节点。
/**
* 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) {
// mode指代独占模式或者共享模式,将当前的线程和传入的模式构造成新的节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//这里是打算用尾插法让新的节点入队
Node pred = tail;
//如果队列不为空的话
if (pred != null) {
//新节点的前驱指向原来的尾节点
node.prev = pred;
//用cas把自己这个node设为尾节点,用cas是为了防止多个线程都想入队,保障 //了线程安全,用cas保证了一次就一个节点进入队列,然后这个节点变成了队列的 //新的尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
//节点入队后,返回包装当前线程的节点
return node;
}
}
//队列为空的话或者上面的cas入队失败,就是下面的入队方式,最终返回的还是包装当 //前线程的节点
enq(node);
return node;
}
private Node enq(final Node node)
cas入队方法
private Node enq(final Node node) {
//先来一个自旋
for (;;) {
//获得等待队列的尾节点
Node t = tail;
//队列为空的情况
if (t == null) { // Must initialize
//初始化其实也是初始化一个dummy节点,头指针和尾指针都指向dummy节 //点,但是这个dummy节点里面是不含线程的,dummy节点只是为了方便操 //作等待队列
if (compareAndSetHead(new Node()))
tail = head;
//队列不为空的情况,那么就cas入队
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//这里才返回,返回的是包装了当前线程的节点
return t;
}
}
}
}
实现不可重入锁
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Slf4j(topic = "ch.TestAqs01")
public class TestAqs01 {
//测试MyLock
public static void main(String[] args) {
MyLock myLock = new MyLock();
new Thread(() -> {
myLock.lock();
log.debug("加锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myLock.unlock();
log.debug("解锁");
}, "t1").start();
new Thread(() -> {
myLock.lock();
log.debug("加锁");
myLock.unlock();
log.debug("解锁");
}, "t2").start();
}
}
@Slf4j(topic = "ch.MyLock")
class MyLock implements Lock {
class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private MySync mySync = new MySync();
//加锁
@Override
public void lock() {
mySync.acquire(1);
}
//加可打断的锁
@Override
public void lockInterruptibly() throws InterruptedException {
mySync.acquireInterruptibly(1);
}
//尝试加锁
@Override
public boolean tryLock() {
return mySync.tryAcquire(1);
}
//带超时的尝试加锁
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return mySync.tryAcquireNanos(1, unit.toNanos(time));
}
//解锁
@Override
public void unlock() {
mySync.release(1);
}
//创建条件变量
@Override
public Condition newCondition() {
return mySync.newCondition();
}
}
AQS的基本思想
获取锁的逻辑
while(state 状态不允许获取) {
if(队列中还没有此线程) {
入队并阻塞
}
}
当前线程出队
释放锁的逻辑
if(state 状态允许了) {
恢复阻塞的线程(s)
}
设计的要点如下:
第一是要原子维护state的状态
state使用volatile配合cas保证修改时的原子性
state采用了32bit的int型数据来维护同步状态,因为long型的数据在很多平台下可能会有线程安全问题。(long可能线程不安全的原因:JVM将32位数据作为原子操作,所以读写long类型的数据,很可能是需要两次的读写操作,如果是多个线程一起操作同一个long型数据,很可能出现高32位和低32位错误的情况)
第二是要设计好阻塞以及恢复线程的方式
*早期的控制线程暂停和恢复api有suspend和resume,但是他们有弊端,如果先调用resume,suspend将感知不到。(resume,suspend过期的线程阻塞启动的方法)
*现在线程的阻塞以及恢复可以通过park和unpark实现
*park和unpark是针对线程的,而不是针对同步器的,控制粒度更加精细(同步器一般是指多线程的控制器,等待一个或者多个线程变成某一状态时,某些线程再开启下一部分的操作)
*park线程还可以通过interrupt打断
第三是做好队列设计
AQS使用了fifo先入先出队列,并不支持优先级队列
设计时借鉴了CLH队列,是一种单向无锁的队列
AQS队列中只有head和tail两个指针节点,都用volatile配置cas使用,保证了多线程下的安全性,每个节点都有state维护节点状态。
ReentrantLock原理
非公平锁的实现原理
加锁以及竞争加锁的流程
首先从构造器开始看,ReentrantLock默认是非公平锁实现。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
NonfairSync继承了Sync,Sync又继承了AbstractQueuedSynchronizer
当没有竞争时,线程直接对资源加锁,把state设为1,然后将owner线程设置为本线程。
在有竞争的情况下,假如线程一尝试用CAS将state由0改为1,结果失败了,
这时就会进入acquire(1)的分支
acquire的方法如下
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先进行的操作还是尝试去获得锁,但是由于state状态为1,所以仍然失败,
失败之后进入addWaiter方法,构造Node队列
addWaiter的主要目的就是建立一个等待资源的队列,其中头节点是个dummy节点,且队列刚初始化时,每个节点的waitStatus都是0
接着线程进入acquireQueued的方法
1.acquireQueued会在一个死循环中不断尝试去获得锁,如果失败了就进入park阻塞
2.如果自己是head节点后的节点,即等待队列的首节点,就会再次去尝试去获得锁,这时state仍为1,所以尝试获取锁失败。
3.获取锁失败后进入的是shouldParkAfterFailedAcquire逻辑,就是把线程park,并且将线程的前驱节点的waitStatus设置为signal。第一次进入shouldParkAfterFailedAcquire逻辑会返回false
4.shouldParkAfterFailedAcquire返回false之后仍会回到acquireQueued中的死循环,这时候由于等待队列只有自己一个真。等待节点,所以会再去获取一次锁,当然由于state仍为1,所以尝试仍旧失败
5.尝试获取锁失败后,又会进入shouldParkAfterFailedAcquire逻辑,这一次由于前驱是signal,所以shouldParkAfterFailedAcquire直接返回true
6.shouldParkAfterFailedAcquire直接返回true后就会进入parkAndCheckInterrupt,当前线程终于进入了park状态
如果有多个线程经历过上面这样的竞争线程失败,就会变成下图:
解锁以及唤醒的流程
thread-0用完资源之后释放锁,进入release流程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);
}
可以看到首先是改变state状态,如果将state状态修改到0,就返回release成功,并且将ExclusiveOwnerThread设置为null
此时当前队列不为空,且head的waitStatus=-1,head要负责唤醒它的后继节点,进入unparkSuccessor流程。
找到队列中离head最近,且waitStatus < 0且不为null的node,然后用LockSupport的unpark方法恢复这个node中线程的运行,这个例子中是thread-1
unpark后,thread-1会进入acquireQueued的流程,
这时如果加锁成功(没有竞争),
会设置ownerThread为thread-1,state=1
head会指向刚刚thread-1所在的node,该node也会清空thread,变成一个dummy型的头节点
原本的head会从链表上断开,从而被垃圾回收
另外一种情况,如果这时候,也有其他线程来竞争锁,假如这时候thread4来了(因为是非公平锁)
thread-4会被设置为ownerthread,state又改成了1
thread-1会再次进入acquireQueued流程,且因为state为1又会竞争锁失败,重新进入park阻塞
注意一点:是否需要unpark是由当前节点的前驱节点的waitStatus是否等于signal决定的,而不是由本节点的waitStatus确定的。
ReentrantLock的可重入原理
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 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;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//支持锁重入,只有state减为0的时候,才释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
ReentrantLock的可打断原理
不可打断模式:在这个模式下,即使线程被打断,仍然会驻留在AQS队列中,一直要等到获得锁之后才知道自己被打断了
private final boolean parkAndCheckInterrupt() {
// 如果打断标记已经是 true, 则 park 会失效
LockSupport.park(this);
// interrupted 会清除打断标记
return Thread.interrupted();
}
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)) {
setHead(node);
p.next = null;
failed = false;
// 还是需要获得锁后, 才能返回打断状态
return interrupted;
}
if (
shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()
) {
// 如果是因为 interrupt 被唤醒, 返回打断状态为 true
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (
!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
// 如果打断状态为 true
selfInterrupt();
}
}
static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
}
}
可打断模式:其他线程一旦打断,就会立即捕获打断异常
static final class NonfairSync extends Sync {
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果没有获得到锁, 进入 (一)
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// (一) 可打断的获取锁流程
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
// 在 park 过程中如果被 interrupt 会进入此
// 这时候抛出异常, 而不会再次进入 for (;;)
throw new InterruptedException();
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
公平锁实现原理
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;
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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());
}
一个简单的demo演示公平锁的原理
import java.util.concurrent.locks.ReentrantLock;
public class TestFairReentrantLock {
public static void main(String[] args) {
//创建一个公平锁
ReentrantLock fairLock = new ReentrantLock(true);
Thread t1 = new Thread(() -> {
fairLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
fairLock.unlock();
}, "t1");
Thread t2 = new Thread(() -> {
fairLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
fairLock.unlock();
}, "t2");
Thread t3 = new Thread(() -> {
fairLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
fairLock.unlock();
}, "t3");
//公平锁就是t2在排队的时候,t1释放锁,t3尝试获得锁,t3必须得后于t2获得锁,才叫公平
t1.start();
t2.start();
t3.start();
}
}
第一步main线程执行到函数末尾,即三个线程都start,但是三个线程的断点都停在fairLock.lock();的状态
第二步让t1线程运行到unlock这一步,但是却不释放锁
第三步,把线程切换到thread-2,单步一直走,进入acquireQueued方法可以发现线程进入等待队列并被park了起来
第四步,thread-1进行unlock,但是thread-2不动,thread-3去尝试获取锁,由于thread-1解锁了,所以thread-3可以读到state的值为0,但是在hasQueuedPredecessors函数中,由于队列中thread2线程在thread-3线程之前,所以thread-3没法执行,thread-3只能进入队列在thread-2之后
//head的next指向thread-2
//thread-2的next指向thread-3
这就是公平锁
条件变量的实现原理
每个条件变量其实就对应着一个等待队列,实现类是ConditionObject
await流程
开始的时候thread-0持有锁,然后调用await函数,进入ConditionObject的addConditionWaiter的流程
创建新的Node状态为Node.CONDITION,关联Thread-0,进入等待队列尾部
然后进入AQS的fullRelease函数,释放同步器上的锁
thread-0所在的节点会unpark AQS中thread-0之后一个节点中的线程,如果没有其他线程竞争锁,那么thread-1竞争锁成功。
signal流程
假设thread-1要来唤醒thread-0
thread-1会进入conditionObject的doSignal流程,取得等待队列中第一个Node,就是thread-0所在的Node
然后执行transferForSignal流程,将该Node加入AQS队列尾部,将Thread-0的waitStatus改为0,Thread-3的waitStatus改为-1.
记录一次await&signal的源码调试
思路
main线程中创建三个Thread,分别是t1,t2,t3,两个condition,分别是conditionA,conditionB,t1,t2进入conditionA等待,t3进入conditionB等待,最后主线程再signalAll conditionA中的线程,signal conditionB中的线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLockAwait {
public static void main(String[] args) {
ReentrantLock myLock = new ReentrantLock();
Condition conditionA = myLock.newCondition();
Condition conditionB = myLock.newCondition();
Thread t1 = new Thread(() -> {
myLock.lock();
try {
Thread.sleep(1000);
conditionA.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myLock.unlock();
}, "t1");
Thread t2 = new Thread(() -> {
myLock.lock();
try {
Thread.sleep(1000);
conditionA.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myLock.unlock();
}, "t2");
Thread t3 = new Thread(() -> {
myLock.lock();
try {
Thread.sleep(1000);
conditionB.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myLock.unlock();
}, "t3");
t1.start();
t2.start();
t3.start();
myLock.lock();
conditionA.signalAll();
conditionB.signal();
myLock.unlock();
}
}
main线程执行完t1,t2,t3的start方法,创建三个线程,然后线程t1,t2,t3分别去竞争myLock。达到t1线程占有锁,t2,t3线程被unpark在等待队列中的状态。
//t1竞争锁成功
//t2竞争锁失败,进入等待队列,自己被park阻塞,进入WAIT状态;t3当然同理且就在等待队列的t2的后面
//t1调用await方法,观察源码实现,首先进入await(),接着进入addConditionWaiter(),在addConditionWaiter方法中,由于t1是第一个调用await方法的线程,所以会创建一个conditionA的等待队列,且waitStatus为CONDITION(-2),然后进入fullyRelease()方法释放锁资源,fullyRelease()中会调用release()方法(主要是tryRelease释放锁,unparkSuccessor唤醒等待锁的线程,在这个例子中就是先唤醒t2),t2被唤醒后没有竞争就会占有锁,并且把head的next置为t3,此时t1在conditionA的等待队列,t2占有锁,t3在等待锁的队列
//然后t2进入conditionA的await方法,此时由于t1在conditionA的等待队列,所以t2只能进入conditionA的等待队列并排在t1后,从图中可以看出firstWaiter是t1,firstWaiter.nextWaiter是t2,接着t2也被park,t3终于获得了锁,然后t3去调用conditionB的await类似之前t1调用conditionA的await方法。这个时候t1,t2在等conditionA,t3在等conditionB,只有main线程运行,我们就可以看看signal的源码了
//最后来看signal的源码,conditionA调用signalAll(),signalAll()先检查main线程是否占有锁,占有才能去做unpark,doSignalAll方法就是把所有等待在conditionA的线程一个接一个的放到等待锁的队列上,signal的操作类似
//第一次循环
//第二次循环
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现