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

  1. 什么是显示条件?
    答:

    • 显示条件在不同上下文也可以被称为:条件变量、条件队列、条件。
    • 锁用于解决竞态条件问题,条件是线程间的协作机制
    • 显示锁与 synchronized 相对应,显示条件与 wait/notify 对应
    • wait/nofitysynchronized 配合使用,显示条件与显示锁配合使用。
    • 条件与锁相关联,创建条件变量需要通过显示锁。Lock 接口定义了创建方法:Condition newCondition()
    • Condition 表示条件变量,是一个接口
    • wait/notify 相比,可以支持多个条件队列,代码更为易读高效
  2. Condition 的定义是?
    答:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface Condition {
    void await() throws InterruptedException; // 对应于 Object 的 wait()
    void awaitUninterruptibly(); // 不响应中断的等待方法,但它返回时,如果等待过程中发生了中断,中断标志位被会设置
    long awaitNanos(long nanosTimeout) throws IntertuptedException; // 等待时间是相对时间,参数单位是纳秒,返回值是 nanosTimeout 减去实际等待的时间
    boolean await(long time, TimeUnit unit) throws InterruptedException; // 等待时间是相对时间,如果由于等待超时返回,返回值为 false,否则为 true
    boolean awaitUntil(Date deadline) throws InterruptedException; // 等待时间是绝对时间,如果由于等待超时返回,返回值为 false,否则为 true
    void signal(); // 对应于 notify()
    void signalAll(); // 对应于 notifyAll()
    }
  1. await() 方法的基本使用?
    答:

    • 一般而言,与 Objectwait() 方法一样,调用 await() 方法前需要先获取锁。如果没有锁,会抛出 IllegalMonitorStateException 异常
    • await() 在进入等待队列后,会释放锁,释放 CPU。当其他行程将它唤醒后,或等待超时后,或发生中断异常后,它都需要重新获取锁,获取锁后,才会从 await() 方法中退出
  2. 【笔试题】手写一个使用显示条件进行协作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
    // 一个线程启动后,在执行一项操作前,等待主线程给它指令,收到指令后才执行
    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) {
    Thread.interrupted(); // 返回当前线程的中断标志位是否为 true,但它还有一个重要的副作用,就是清空中断标志位。也就是说,连续两次调用 interrupted(),第一次返回的结果为 true,第二次一般就是 false(除非同时又发生了一次中断)
    }
    }
    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();
    }
    }
  1. 【笔试题】手写一个使用显示锁/条件实现的阻塞队列(生产者/消费者) 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
    41
    42
    // 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 就采用了类似的方式实现