0%

Java 并发包的基石(三):显示条件

1. 什么是显示条件

  • 显示条件在不同上下文也可以被称为:条件变量条件队列条件

    • 锁用于解决竞态条件问题,条件是线程间的协作机制
    • 显示锁与 synchronized 相对应,显示条件与 wait/notify 对应
    • wait/nofitysynchronized 配合使用,显示条件与显示锁配合使用
  • 条件与锁相关联,创建条件变量需要通过显示锁。Lock 接口定义了创建方法:Condition newCondition()

    • Condition 表示条件变量,是一个接口
  • wait/notify 相比,可以支持多个条件队列,代码更为易读高效

2. Condition 的定义是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Condition {
//对应于 Object 的 wait()
void await() throws InterruptedException;

//不响应中断的等待方法,但它返回时,如果等待过程中发生了中断,中断标志位被会设置
void awaitUninterruptibly();

//等待时间是相对时间,参数单位是纳秒,返回值是 nanosTimeout 减去实际等待的时间
long awaitNanos(long nanosTimeout) throws IntertuptedException;

//等待时间是相对时间,如果由于等待超时返回,返回值为 false,否则为 true
boolean await(long time, TimeUnit unit) throws InterruptedException;

//等待时间是绝对时间,如果由于等待超时返回,返回值为 false,否则为 true
boolean awaitUntil(Date deadline) throws InterruptedException;

//对应于 notify()
void signal();

//对应于 notifyAll()
void signalAll();
}

3. await() 方法的基本使用

  • 一般而言,与 Objectwait() 方法一样,调用 await() 方法前需要先获取锁。如果没有锁,会抛出 IllegalMonitorStateException 异常
  • await() 在进入等待队列后,会释放锁,释放 CPU。当其他行程将它唤醒后,或等待超时后,或发生中断异常后,它都需要重新获取锁,获取锁后,才会从 await() 方法中退出

4. 写一个使用显示条件进行协作的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//一个线程启动后,在执行一项操作前,等待主线程给它指令,收到指令后才执行
public class WaitThread extends Thread {
private volatile boolean fire = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
try {
while(!fire) {
condition.await();
}
} finally {
lock.unlock();
}
System.out.println("fired");
} catch(InterruptedException e) {
//返回当前线程的中断标志位是否为 true,但它还有一个重要的副作用,就是清空中断标志位
//也就是说,连续两次调用 interrupted(),第一次返回的结果为 true,第二次一般就是 false(除非同时又发生了一次中断)
Thread.interrupted();
}
}
public void fire() {
lock.lock();
try {
this.fire = true;
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
System.out.println("fire");
waitThread.fire();
}
}

5. 写一个使用显示锁/条件实现的阻塞队列(生产者/消费者) Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//wait/notify 的局限是它只能有一个条件等待队列,分析等待条件也很复杂
//在生产者/消费者模式中,其实有两个条件,一个与队列满有关,一个与队列空有关
//使用显示锁,可以创建多个条件等待队列
static class MyBlockingQueue<E> {
private Queue<E> queue = null;
private int limit;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public MyBlockingQueue(int limit) {
this.limit = limit;
queue = new ArrayDeque<> (limit);
}
public void put(E e) throws InterruptedException {
lock.lockInterruptibly();
try {
while(queue.size() == limit) {
notFull.await();
}
queue.add(e);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
E e = queue.poll();
notFull.signal();
return e;
} finally {
lock.unlock();
}
}
}
  • 清晰易读,同时避免了不必要的唤醒和检查,提高了效率
  • Java 并发包中的类 ArrayBlockingQueue 就采用了类似的方式实现
-------------------- 本文结束感谢您的阅读 --------------------