一、简介
JUC提供了Lock可以方便的进行锁操作,但是有时候我们也需要对线程进行条件性的阻塞和唤醒,这时我们就需要Condition条件变量,可以方便的对持有锁的线程进行阻塞和唤醒。
Condition将对象监视器方法(wait、notify和notifyAll)分解到不同的对象中,通过将它们与任意锁实现结合使用,实现每个对象具有多个等待集的效果。锁代替同步方法和语句的使用,Condition代替对象监视器方法的使用。
Condition(也称为条件队列或条件变量)提供了一种方法,让一个线程暂停执行(“等待”),直到另一个线程通知某个状态条件现在可能为真。因为对共享状态信息的访问发生在不同的线程中,所以必须保护它,所以某种形式的锁与条件相关联。等待条件提供的关键属性是它自动释放关联的锁并挂起当前线程,就像Object.wait一样。
Condition实例本质上绑定到锁。要获取特定锁实例的条件实例,请使用其newCondition()方法。
- 例如,假设我们有一个有界的缓冲区,它支持put和take方法。如果尝试在空缓冲区上执行take操作,则线程将阻塞,直到某个项可用为止;如果在一个完整的缓冲区上尝试put,那么线程将阻塞,直到空间可用为止。我们希望将put线程和take线程放在不同的等待集中,这样我们就可以优化在缓冲区中的项或空间可用时只通知单个线程。这可以通过使用两个Condition条件实例来实现。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
//非满的条件
final Condition notFull = lock.newCondition();
//非空的条件
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
//如果满了,则阻塞等待
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
//通知take
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
//如果当前为空, 则等待
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
//通知需要进行put
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
二、常用API
void |
await() 导致当前线程等待,直到发出信号或中断它。 |
boolean |
await(long time, TimeUnit unit) 导致当前线程等待,直到发出信号或中断它,或指定的等待时间过期。 |
long |
awaitNanos(long nanosTimeout) 导致当前线程等待,直到发出信号或中断它,或指定的等待时间过期。 |
void |
导致当前线程等待,直到它被通知。 |
boolean |
awaitUntil(Date deadline) 导致当前线程等待,直到发出信号或中断它,或指定的截止日期过期。 |
void |
signal() 唤醒一个正在等待的线程。 |
void |
唤醒所有等待的线程。 |
更加详细的介绍,可以去官网https://docs.oracle.com/javase/8/docs/api/index.html了解。
三、案例
下面通过一个精确通知顺序访问的案例加深对Condition的使用。
示例:多线程之间按顺序调用,实现AA->BB->CC依次打印。
案例分析:
要实现按顺序打印,那么是否要有一些标志,标志当前需要打印AA还是BB还是CC:
/**
* 标志位
* A :1
* B : 2
* C : 3
*/
private int number = 1;
当打印完AA后,将number修改为2,打印完BB后,将n的umber修改为3,打印完CC后将number修改为1,依次重复,然后再创建三个分别代表打印AA/BB/CC的Condition绑定到Lock上。
代码实现:
/**
* 多线程之间按顺序调用,实现AA->BB->CC依次打印
* 也即精确通知顺序访问
*/
public class T07_ConditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printAA();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printBB();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printCC();
System.out.println("===================");
}
}).start();
}
}
class ShareData {
/**
* 标志位
* A :1
* B : 2
* C : 3
*/
private int number = 1;
/**
* 可重入锁
*/
private Lock lock = new ReentrantLock();
/**
* 打印AA的条件
*/
private Condition conditionA = lock.newCondition();
/**
* 打印BB的条件
*/
private Condition conditionB = lock.newCondition();
/**
* 打印CC的条件
*/
private Condition conditionC = lock.newCondition();
public void printAA() {
lock.lock();
try {
while (number != 1) {
conditionA.await();
}
for (int i = 0; i <= 4; i++) {
System.out.println("AA");
}
number = 2;
//唤醒打印BB的线程
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printBB() {
lock.lock();
try {
while (number != 2) {
conditionB.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("BB");
}
number = 3;
//唤醒打印CC的线程
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printCC() {
lock.lock();
try {
while (number != 3) {
conditionC.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("CC");
}
number = 1;
//唤醒打印AA的线程
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果:
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
AA
AA
AA
AA
AA
===================
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
以上就是使用Condition实现了精确通知某个线程然后按顺序访问。