一、线程通信 Condition
在使用synchronized时,我们结合wait、notify、notifyAll可以实现线程的通信,其中wait方法会阻塞当前线程并释放锁,notify/notifyAll会唤醒等待锁的线程并抢占锁。JUC中也为我们提供了一个多线程协调通信的工具类Condition,可以让竞争同一把锁的多个线程之间互相唤醒通信。
java.util.concurrent.locks.Condition#await()
方法和java.lang.Object#wait()
方法相似,会阻塞当前线程并释放锁;
java.util.concurrent.locks.Condition#signal、java.util.concurrent.locks.Condition#signalAll
和java.lang.Object#notify、java.lang.Object#notifyAll
方法相似,会唤醒等待锁的线程并去抢占锁。
1. Condition基本使用
我们会以生产者、消费者为例实现线程的互相通信。本例中会创建生产者、消费者两个线程,两者公用同一个队列、同一把锁。当生产者生产数据并将队列填充满时,会挂起并通知消费者来消费数据;当消费者将队列中数据消费完毕,会挂起消费者线程并唤醒生产者来生产,依次循环。
实现逻辑与之前synchronized、wait、notify的例子一致:Synchronized 线程通信
生产者 : 负责生产数据添加至队列中去并通知消费者,当队列已满则会停止生产挂起线程并释放锁,此时之前通知的线程会去竞争锁
import java.io.Serializable;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Producer extends Thread implements Serializable {
Lock lock;
Condition condition;
Queue<String> queue;
int maxSize;
int i = 0;
public Producer(Lock lock, Condition condition, Queue<String> queue, int maxSize) {
this.lock = lock;
this.condition = condition;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
lock.lock();
try {
while (true) {
i++;
if (queue.size() == maxSize) {
// 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
System.out.println("队列已满,Producer 生产者阻塞并释放锁");
condition.await();
}
String msg = "Producer 生产消息 " + i;
System.out.println(msg);
queue.add(msg);
// 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
condition.signal();
// condition.signalAll();
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 除非出异常,否则不会调用这里的unlock来释放锁
System.out.println("Producer 释放锁");
lock.unlock();
}
}
}
消费者 :负责从队列中取出消费来消费,当队列为空则挂起当前线程并释放锁
import java.io.Serializable;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Consumer extends Thread implements Serializable {
Lock lock;
Condition condition;
Queue<String> queue;
int maxSize;
public Consumer(Lock lock, Condition condition, Queue<String> queue, int maxSize) {
this.lock = lock;
this.condition = condition;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
lock.lock();
try {
while (true) {
if(queue.isEmpty()) {
// 队列为空,消费者阻塞,释放锁,唤醒其余竞争该锁的线程 即唤醒生产者线程
System.out.println("队列为空,Consumer 消费者阻塞并释放锁");
condition.await();
}
String msg = queue.poll();
System.out.println("Consumer 消费消息: " + msg);
// 通知
condition.signal();
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 除非出异常,否则不会调用这里的unlock来释放锁
System.out.println("Consumer 释放锁");
lock.unlock();
}
}
}
测试类
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MainTest {
public static void main(String[] args) {
// 生产者、消费者使用同一把锁
ReentrantLock lock = new ReentrantLock();
// 同一个condition条件
Condition condition = lock.newCondition();
Queue queue = new LinkedList();
int maxSize = 5;
Producer producer = new Producer(lock, condition, queue, maxSize);
Consumer consumer = new Consumer(lock, condition, queue, maxSize);
producer.start();
consumer.start();
}
}
...运行结果
队列为空,Consumer 消费者阻塞并释放锁
Producer 生产消息 1
Producer 生产消息 2
Producer 生产消息 3
Producer 生产消息 4
Producer 生产消息 5
队列已满,Producer 生产者阻塞并释放锁
Consumer 消费消息: Producer 生产消息 1
Consumer 消费消息: Producer 生产消息 2
Consumer 消费消息: Producer 生产消息 3
Consumer 消费消息: Producer 生产消息 4
Consumer 消费消息: Producer 生产消息 5
队列为空,Consumer 消费者阻塞并释放锁
Producer 生产消息 6
Producer 生产消息 7
Producer 生产消息 8
Producer 生产消息 9
Producer 生产消息 10
队列已满,Producer 生产者阻塞并释放锁
Consumer 消费消息: Producer 生产消息 6
Consumer 消费消息: Producer 生产消息 7
Consumer 消费消息: Producer 生产消息 8
...
...
2. Condition队列模型
Condition等待队列基本结构
AQS同步队列与Codnition等待队列
3. Condition源码分析
分析源码之前,首先理一下基本概念:
Condition是基于JUC内Lock创建的,实现Lock的核心组件是AQS(AbstractQueuedSynchronizer),本质上锁的竞争是对一个共享变量state的修改,没有获得锁的线程封装为Node存储于AQS的一个双向链表的同步队列里;
Condition.await()会做四件事情:a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;
Condition.signal()会将 Condition队列 中第一个节点firstWaiter从 Condition队列 转移至 AQS同步队列,但是并不会唤醒线程;
在其它线程释放锁后,AQS会从 AQS同步队列 中查找合适的head节点并唤醒;
AQS同步队列 : 一个双向链表,节点为Node{prev, next, thread},存放没有竞争到锁的线程
Condition等待队列 : 一个单向链表,节点也为Node{nextWaiter, thread}, 存放condition.await()的线程
结合上面生产者和消费者例子来对每个步骤进行源码分析:
-
首先main方法中Producer和Consumer线程同时启动,所以可能是其中任何一个线程会先获得锁,根据上面运行结果很明显是Consumer线程先获得了锁;
-
Consumer此时获取了锁,则Producer竞争锁失败,Producer线程封装为Node节点放入ReentrantLock的AQS同步队列中并阻塞;
-
Consumer中发现queue为空,没有消费数据所以调用await, await会 a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;
consumer
if(queue.isEmpty()) {
// 队列为空,消费者阻塞,释放锁,唤醒其余竞争该锁的线程 即唤醒生产者线程
System.out.println("队列为空,Consumer 消费者阻塞并释放锁");
condition.await();
}
await源码:
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 线程是否被中断过 false
throw new InterruptedException();
Node node = addConditionWaiter(); // a.将当前线程封装为Condition类型节点并存放于Condition队列
int savedState = fullyRelease(node); // b.释放锁、c.从AQS队列中唤醒等待的线程
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 当前节点是否在AQS同步队列中, 明显不在
LockSupport.park(this); // d.阻塞当前线程; 至此,await中consumer线程完成了a、b、c、d四个步骤
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
...
// a. 封装为一个Condition节点并加入Condition队列中去
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) { // 此时lastWaiter为null
unlinkCancelledWaiters(); // 这里如果 lastWaiter.waitStatus不是CONDITION状态,表示该线程被中断过Thread.interrupter(),需要清除Condition队列中的脏数据
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 封装producer为Condition节点
if (t == null)
firstWaiter = node; // 指定当前节点为 firstWaiter
else
t.nextWaiter = node;
lastWaiter = node; // 新加的节点默认为 lastWaiter
return node;
}
...
// b.释放这个线程的全部重入锁 c.唤醒等待的线程
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 获取重入次数
if (release(savedState)) { // 释放锁, 重置AQS的state状态为0,并将占有线程置为null,
failed = false;
return savedState; // 返回重入次数
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// b.释放这个线程的全部重入锁 c.唤醒等待的线程
public final boolean release(int arg) {
if (tryRelease(arg)) { // 此时state为0,可以释放锁,
Node h = head; // 拿到AQS队列head节点 ,此时head为一个new Node()默认节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // c. 唤醒节点
return true;
}
return false;
}
// b. 释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 相减为0
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 重置占有锁的线程为null
}
setState(c); // 重置state为0,也就是释放了锁
return free;
}
// c. 唤醒节点
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) // 此时head.waitStatus 为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; // next节点即为之前阻塞的producer节点
if (s == null || s.waitStatus > 0) { // s.waitStatus=-1
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); // 唤醒producer线程
}
...
// 是否在同步队列中 false
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null) // 此时node为CONDITION节点
return false; // 直接返回false
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
...
d.阻塞当前线程
LockSupport.park(this); // 挂起线程
//至此,await中consumer线程完成了: 将consumer线程封装为Node存入Condition队列中、释放了锁、唤醒了producer线程、并将当前consumer线程阻塞
4.producer线程被唤醒并获得锁,判断队列未满,生产消息并signal通知Consumer条件已满足
在Consumer的await()内,已经将锁释放并将producer线程唤醒,则producer线程从AQS的之前park的逻辑开始执行
首先是在lock()中被阻塞的
producer
@Override
public void run() {
lock.lock();
try {
while (true) {
...
}
...
}
lock()被阻塞的源码,现在重新唤醒来抢占锁
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 第3步中unpark唤醒,继续执行
return Thread.interrupted(); // producer线程没有被中断过,返回false
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // AQS同步队列中 producer的上一节点
if (p == head && tryAcquire(arg)) { // 此时可以成功抢夺锁
setHead(node); // 把自身,即producer置为head节点
p.next = null; // help GC
failed = false;
return interrupted; // 至此返回, consumer线程继续 lock.lock()的后续逻辑
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
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) {
if (compareAndSetState(0, acquires)) { // state 0 -> 1
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;
}
producer线程继续 lock.lock()的后续逻辑,队列未满继续生产、并signal通知consumer线程 将consumer线程从Condition队列转移至AQS的同步队列中
@Override
public void run() {
lock.lock();
try {
while (true) {
i++;
if (queue.size() == maxSize) {
// 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
System.out.println("队列已满,Producer 生产者阻塞并释放锁");
condition.await();
}
String msg = "Producer 生产消息 " + i;
System.out.println(msg);
queue.add(msg);
// 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
condition.signal();
// condition.signalAll();
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 除非出异常,否则不会调用这里的unlock来释放锁
System.out.println("Producer 释放锁");
lock.unlock();
}
}
在将消息加入到队列后,会调用condition.signal();
通知到condition队列:
signal源码:
// 通知Condition对象
public final void signal() {
if (!isHeldExclusively()) // 当前线程是否持有排他锁, 是的, !返回false
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 这里的firstWaiter即为consumer节点,因为consumer线程调用await()时已把自己加入到Condition队列中去
if (first != null)
doSignal(first); // 通知firstWaiter节点
}
// 是否持有排他锁
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread(); // 此时AQS中锁是producer持有,返回true
}
// 通知firstWaiter节点
private void doSignal(Node first) {
do {
//如果通知节点转移至AQS队列失败,说明该节点状态有误,需要从CONDITION等待队列移除
// 这个逻辑把firstwaiter节点从队列中移除
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && //如果transferForSignal方法成功将节点转移至AQS同步队列中,则停止循环
(first = firstWaiter) != null);
}
// 将Condition节点转为Signal节点,并从Condition队列转移至AQS的同步队列
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 将consumer节点的waitStatus状态由 CONDITION 转为 0 默认状态
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); // AQS重新入队,返回前一节点 即默认的 new Node()
int ws = p.waitStatus; // waitStatus为初始值 0
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true; // 返回true
}
// 重新入队 自旋加入AQS队列的队尾tail
private Node enq(final Node node) {
for (;;) { // 自旋
Node t = tail;
if (t == null) { // Must initialize 当tail为null,表示AQS队列为空,必须初始化 head、tail节点
if (compareAndSetHead(new Node())) //为head初始值 new Node()
tail = head; // tail赋初始值 new Date()
} else {
node.prev = t; // 新加入的node(即为consumer)要在AQS队列中排队,node的prev即为原尾节点
if (compareAndSetTail(t, node)) { // CAS操作将tail由原来的t替换为现在的node
t.next = node; // 双向链表,原尾节点现在是倒数第二,t.next指向当前尾节点
return t; // 返回前一节点
}
}
}
}
-
之后Producer判断已满,调用await,仍是4步:a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;
a. 将producer封装为Condition类型的Node节点并放入Condition队列
b. 释放producer线程占有的锁
c. 从AQS队列中唤醒等待的线程,即 consumer线程
d. 阻塞producer队列 -
Consumer消费者唤醒,消费数据并signal通知Producer条件已满足,从Condition队列移至AQS的抢夺锁的同步队列中去
consumer线程unpark唤醒时,从await的park处继续执行:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // consumer线程在AQS中unpark唤醒,从这里继续执行
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 判断该线程在等待过程中是否被中断了
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
consumer消费消息
@Override
public void run() {
lock.lock();
try {
while (true) {
i++;
if (queue.size() == maxSize) {
// 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
System.out.println("队列已满,Producer 生产者阻塞并释放锁");
condition.await();
}
String msg = "Producer 生产消息 " + i;
System.out.println(msg);
queue.add(msg);
// 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
condition.signal();
// condition.signalAll();
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 除非出异常,否则不会调用这里的unlock来释放锁
System.out.println("Producer 释放锁");
lock.unlock();
}
}
-> Consumer发现队列为空,await阻塞当前线程并释放锁 -> AQS后续调度
-> 继续循环
...
...
总结:Conditon.await、Condition.signal
根据以上源码分析可得出:
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()内实现了4件事情:
a.将当前线程封装为Condition类型节点并存放于Condition队列、
b.释放锁、
c.从AQS队列中唤醒等待的线程、
d.阻塞当前线程;
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#signal内实现了1件事情:
从Condition队列中将firstWaiter 节点转至AQS同步队列中去,且节点Node类型由Condition转为默认状态0
至于await的线程什么时候被唤醒,要结合signal、lock.unlock()或者启停线程await()通过AQS同步队列来执行LockSupport.unpark唤醒;
当然,除了调用java层面的unpark,也可能是调用了线程的thread.interrupt()方法来中断线程触发的,interrupt()会更新线程的中断标识并且唤醒处于阻塞下的线程。