关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch
工作原理概要
在并发编程中,每个Java对象都有一组监视器方法,如wait()、notify()和notifyAll(),通过这些方法,可以实现等待唤醒机制,但是这些方法必须配合synchronized关键字使用。Condition同样通过await()、signal()、signalAll()实现了等待唤醒机制,相比原有的一组方法,Condition更加灵活。notify()唤醒的线程是随机的,而signal()可以控制唤醒的线程。
总结如下:
1、Condition可以精确控制多线程的休眠与唤醒
2、对于同一个锁,可以建立为多个线程建立不同的Condition
使用demo
这是一个生产者-消费者模式,生产者生产烤鸭,消费者消费烤鸭。对于生产者来讲,如果存在烤鸭,生产者停止生产,等待消费者消费。对于消费者来讲,如果没有烤鸭,停止消费,等待生产者生产。
public class ResourceByCondition {
private String name;
private int count = 1;
// 当前有烤鸭的标志
private boolean flag = false;
//创建一个锁对象。
Lock lock = new ReentrantLock();
//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producerCondition = lock.newCondition();
Condition consumerCondition = lock.newCondition();
// 生产
public void product(String name) {
lock.lock();
try {
// while循环防止消费线程没有获取到锁, 从而无法消费
// 消费者线程获取到锁, flag会更新成false
while (flag) {
try {
// 当前线程释放锁, 加入等待队列中等待
producerCondition.await();
} catch (InterruptedException e) {
}
}
this.name = name + ", 编号为: " + count;
count++;
System.out.println("生产者线程: "+Thread.currentThread().getName() + ", 生产: " + this.name);
// 生产烤鸭完成, 更新标志为有
flag = true;
// 生产成功, 唤醒消费线程去消费
consumerCondition.signal();
} finally {
lock.unlock();
}
}
// 消费
public void consume() {
lock.lock();
try {
// 这里道理是一样的, 防止生产线程没有获取到锁
while (!flag) {
try {
consumerCondition.await();
} catch (InterruptedException e) {
}
}
//消费烤鸭
System.out.println("消费者线程: " + Thread.currentThread().getName() + ", 消费: " + this.name);
// 消费烤鸭完成, 更新标志为无
flag = false;
// 消费成功, 唤醒生产线程继续生产
producerCondition.signal();
} finally {
lock.unlock();
}
}
}
public class Mutil_Producer_ConsumerByCondition {
public static void main(String[] args) {
ResourceByCondition r = new ResourceByCondition();
Mutil_Producer pro = new Mutil_Producer(r);
Mutil_Consumer con = new Mutil_Consumer(r);
//生产者线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
//消费者线程
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
//启动线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//decrition 生产者线程
class Mutil_Producer implements Runnable {
private ResourceByCondition resourceByCondition;
Mutil_Producer(ResourceByCondition resourceByCondition) {
this.resourceByCondition = resourceByCondition;
}
public void run() {
while (true) {
resourceByCondition.product("北京烤鸭");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// decrition 消费者线程
class Mutil_Consumer implements Runnable {
private ResourceByCondition resourceByCondition;
Mutil_Consumer(ResourceByCondition resourceByCondition) {
this.resourceByCondition = resourceByCondition;
}
public void run() {
while (true) {
resourceByCondition.consume();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果为:
可以看到生产消费依次进行。
主要方法
/* 使当前线程进入等待状态,直到被通知(signal)或中断,相当于Object wait()方法
* 当其他线程调用signal()或singnal()方法时,该线程被唤醒
* 当其他线程调用interrupt()方法时,该线程被中断
*/
void await() throws InterruptedException;
// 唤醒一个在Condition上等待的线程,该线程从等待方法返回前必须获取与Condition关联的锁,相当于Object notify()方法
void signal();
// 唤醒所有在Condition上等待的线程,该线程从等待方法返回前必须获取与Condition关联的锁,相当于Object notifyAll()方法
void signalAll();
原理概述
Condition的具体实现类是AQS的内部类ConditionObject,如下:
// 等待队列中的第一个节点
private transient Node firstWaiter;
// 等待队列中最后一个节点
private transient Node lastWaiter;
AQS中存在同步队列和等待队列,Condition使用的就是等待队列。等待队列是单向的,Node节点的nextWaiter指针指向队列中的后继节点,Condition通过firstWaiter和lastWaiter来表示队列中的头节点和尾节点。等待队列中节点的等待状态只有两种,CANCELLED和CONDITION,前者表示无效节点,需要被移除,后者表示节点等待被唤醒。
一个Condition代表一个等待队列,可以同时存在多个Condition,即存在多个等待队列,但是同步队列只有一个。
对比项 | 同步队列 | 等待队列 |
---|---|---|
节点类型 | Node | Node |
方向 | 双向(prev、next) | 单向(nextWaiter) |
首尾节点 | head(无效节点,不含有线程)、tail | firstWaiter、lastWaiter |
等待状态(waitStatus) | SIGNAL、CANCELLED、PROPAGATE | CONDITION、CANCELLED |
Condition 进入等待状态解析
1、await进入等待状态入口
public final void await() throws InterruptedException {
// 如果当前线程被中断, 抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程封装成等待节点加入等待队列, 并返回
Node node = addConditionWaiter();
// 释放当前线程持有的锁, 即释放同步状态
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断node节点是否在同步队列中
while (!isOnSyncQueue(node)) {
// 如果node节点不在同步队列中
// 挂起线程
LockSupport.park(this);
// 判断是否被中断唤醒, 如果是则退出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//被唤醒后执行自旋操作争取获得锁,同时判断线程是否被中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果又有新的节点加入到等待队列中
if (node.nextWaiter != null)
// 清理等待队列中不为CONDITION状态的节点
unlinkCancelledWaiters();
// 中断模式不为0, 即发生过中断
if (interruptMode != 0)
// 根据中断模式判断, 如果为THROW_IE, 需要抛出异常, 如果为REINTERRUPT, 需要自我中断
reportInterruptAfterWait(interruptMode);
}
2、addConditionWaiter将当前线程封装成等待节点加入等待队列, 并返回
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果等待队列中尾节点状态为结束状态, 即尾节点无效
if (t != null && t.waitStatus != Node.CONDITION) {
// 解绑无效的等待节点, 生成新的等待队列
unlinkCancelledWaiters();
// t赋值为新的尾节点
t = lastWaiter;
}
// 将当前线程封装为等待节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 如果尾节点为null, 即等待队列为空, 将头节点赋值为当前节点
if (t == null)
firstWaiter = node;
else
// 尾节点不为null, 则将尾节点的后继指针指向当前节点
t.nextWaiter = node;
// 当前节点设置为新的尾节点
lastWaiter = node;
return node;
}
3、unlinkCancelledWaiters解绑无效的等待节点, 生成新的等待队列
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
// 在循环中的新队列尾节点
Node trail = null;
while (t != null) {
// 获取t的后继节点
Node next = t.nextWaiter;
// 如果t节点等待状态无效
if (t.waitStatus != Node.CONDITION) {
// 将t从链表中断开
t.nextWaiter = null;
// 新队列为空
if (trail == null)
// 将next赋值给新的头节点
firstWaiter = next;
else
// 新队列不为空, 将尾节点的后继节点指向next
trail.nextWaiter = next;
// next为空, 即已经循环到最后一个节点了
if (next == null)
// 将新队列的尾节点赋值为trail
lastWaiter = trail;
}
else
// t节点为有效节点, trail赋值为t
trail = t;
// 继续下次循环
t = next;
}
}
4、fullyRelease释放当前线程持有的锁
// 完全释放排它锁
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 获取同步状态
int savedState = getState();
// 释放排它锁
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
// 出现异常, 将该节点的状态设置为无效状态
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
5、isOnSyncQueue判断node节点是否在同步队列中
final boolean isOnSyncQueue(Node node) {
// 等待状态为CONDITION或没有prev(同步队列前驱节点指针), 则不在同步队列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 存在next(同步队列后继节点指针), 则在同步队列中
if (node.next != null)
return true;
// 如果node节点还未加入到队列中, 但是node的prev已经设置为同步队列中的节点了,即将加入队列中
// 遍历同步队列查找
return findNodeFromTail(node);
}
// 遍历同步队列查找
private boolean findNodeFromTail(Node node) {
// 从尾节点开始遍历, 找到节点返回true, 找不到返回false
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
如果节点node不在同步队列中,则说明node已经释放锁了,并且进入了等待队列,将线程挂起,然后等待唤醒即可。
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
这段代码中,第一行已经将当前线程挂起了,暂时不会继续向下执行了。如果执行到第二行,说明当前线程被其他线程唤醒了,唤醒之后,需要先检查这段时间内当前线程是否被中断,保证线程安全。
如果线程被中断过,将node加入到同步队列中;如果未被中断,跳出循环。
6、checkInterruptWhileWaiting检查挂起期间是否被中断
private int checkInterruptWhileWaiting(Node node) {
// 如果线程被中断, 将其加入同步队列中, 加入失败返回需要抛出异常, 加入成功返回需要重新中断
// 如果线程未被中断, 返回0
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
// 将node的等待状态更新成0, 更新成功加入同步队列, 返回true
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 更新失败, 说明node节点的等待状态为取消状态, 如果不在同步队列中, 让出CPU执行权,则一直循环等待其他线程将node节点加入到同步队列中
while (!isOnSyncQueue(node))
Thread.yield();
// 返回失败
return false;
}
// 退出等待时重新中断, 从等待状态切换为中断状态
private static final int REINTERRUPT = 1;
// 退出等待时抛出InterruptedException
private static final int THROW_IE = -1;
如果线程为中断状态,会进入transferAfterCancelledWait,会将node的等待状态更新为初始状态0,更新成功,会加入到同步队列中。
如果更新失败,说明等待状态为取消状态,循环等待其他线程将node节点加入到同步队列中。
7、节点回到竞争状态,处理线程状态
// 走到这里说明node节点已在同步队列中, 被唤醒后执行自旋操作争取获得锁,同时判断线程是否被中断
// 申请到锁才会继续执行后续的代码
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// nextWaiter不为空,说明又有新的节点加入到等待队列中
if (node.nextWaiter != null)
// 清理等待队列中不为CONDITION状态的节点
unlinkCancelledWaiters();
// 中断模式不为0, 即发生过中断
if (interruptMode != 0)
// 根据中断模式判断, 如果为THROW_IE, 需要抛出异常, 如果为REINTERRUPT, 需要自我中断
reportInterruptAfterWait(interruptMode);
节点加入到同步队列中,并开始自旋申请锁,就开始了正常获取锁的流程了。
接下来要处理的是线程状态,interruptMode != THROW_IE,线程获取锁成功之后才会走到这里。不等于异常,说明线程未被中断,或者等待状态已经由CONDITION更新为初始状态0,将interruptMode设置为REINTERRUPT,即未来还会让线程中断。
8、reportInterruptAfterWait处理中断状态
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 需要抛出异常,则抛出异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
// 需要再次中断,则中断
selfInterrupt();
}
Condition 唤醒节点解析
1、signal唤醒节点入口
public final void signal() {
// 判断当前线程是否持有排它锁, 不是的话抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取等待队列中的头节点
Node first = firstWaiter;
// 头节点不为null, 唤醒头节点
if (first != null)
doSignal(first);
}
2、doSignal唤醒节点
private void doSignal(Node first) {
do {
// 移除等待队列中的头节点, 将头节点的后继节点赋值为新的头节点
// 如果新的头节点为空, 即等待队列只有一个节点
if ( (firstWaiter = first.nextWaiter) == null)
// 将新的尾节点设置为空
lastWaiter = null;
// 原头节点从队列中移除
first.nextWaiter = null;
// 唤醒头节点, 唤醒失败, 则继续向后取等待节点唤醒, 直至成功
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
3、transferForSignal唤醒等待队列中的node节点
// 唤醒等待队列中的node节点
final boolean transferForSignal(Node node) {
// 将node节点的等待状态更新为0, 更新失败, 说明node节点的状态为取消状态, 返回唤醒失败
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将node节点加入同步队列, 返回node节点的前驱节点
Node p = enq(node);
// 获取前驱节点的等待状态
int ws = p.waitStatus;
// 如果ws大于0, 即前驱节点在同步队列为无效状态, 以后也无法唤醒node节点
// 如果更新前驱节点等待状态失败, 以后也无法唤醒node节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 直接唤醒node节点的线程, 申请锁
LockSupport.unpark(node.thread);
return true;
}
Condition 唤醒所有节点解析
1、signalAll唤醒所有节点入口
public final void signalAll() {
// 判断当前线程是否持有排它锁, 不是的话抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
// 头节点不为null, 从头节点开始唤醒所有节点
if (first != null)
doSignalAll(first);
}
2、doSignalAll唤醒所有节点
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
// 队列不为空,循环唤醒等待队列中的所有节点
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
部分内容参考zejian老师的博客, 博客地址。