Java锁的条件队列

概述

在某些情况下,线程成功获取到锁后发现仍需等待某些条件才能继续往下执行,这时该线程可以把锁释放并进入条件队列,等待持有该锁的其它线程使等待的条件满足,并再次唤醒在条件队列中的线程,被唤醒的线程会再次参与锁的竞争

监视器锁的条件队列

一个监视器锁只有一个条件队列

核心方法

  • object.wait() 当前线程把锁释放并进入条件队列
  • object.notify() 唤醒条件队列中的任意一条线程
  • object.notifyAll() 唤醒条件队列中的所有线程

例子

public class BoundedBuffer<V> {
    private final V[] buf;
    private int tail;
    private int head;
    private int count;
    
    public BoundedBuffer(int capacity) {
        this.buf = (V[]) new Object[capacity];
    }

    private void doPut(V v) {
        buf[tail] = v;
        if (++tail == buf.length) {
            tail = 0;
        }
        ++count;
    }

    private V doTake() {
        V v = buf[head];
        buf[head] = null;
        if (++head == buf.length) {
            head = 0;
        }
        --count;
        return v;
    }

    private boolean isFull() {
        return count == buf.length;
    }

    private boolean isEmpty() {
        return count == 0;
    }

    public synchronized void put(V v) throws InterruptedException {
        while (isFull()) {
            // 向缓冲区放入元素时,缓冲区已满,需等待
            wait();
        }
        doPut(v);
        // 放入元素后,唤醒等待队列中的全部线程,目的在唤醒等待缓冲区非空的全部线程
        notifyAll();
    }

    public synchronized V take() throws InterruptedExecption {
        while (isEmpty()) {
            // 从缓冲区取出元素时,缓冲区已空,需等待
            wait();
        }
        V v = doTake();
        // 取出元素后,唤醒等待队列中的全部线程,目的在唤醒等待缓冲区非满的全部线程
        notifyAll();
        return v;
    }
}

显式锁的条件队列

一个显式锁可以有多个条件队列

核心方法

  • condition.await() 当前线程把锁释放并进入条件队列
  • condition.signal() 唤醒条件队列中的任意一条线程
  • condition.signalAll() 唤醒条件队列中的所有线程

例子

public class BoundedBuffer<V> {
    private final V[] buf;
    private int tail;
    private int head;
    private int count;

    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    
    public BoundedBuffer(int capacity) {
        this.buf = (V[]) new Object[capacity];
    }

    private void doPut(V v) {
        buf[tail] = v;
        if (++tail == buf.length) {
            tail = 0;
        }
        ++count;
    }

    private V doTake() {
        V v = buf[head];
        buf[head] = null;
        if (++head == buf.length) {
            head = 0;
        }
        --count;
        return v;
    }

    private boolean isFull() {
        return count == buf.length;
    }

    private boolean isEmpty() {
        return count == 0;
    }

    public void put(V v) throws InterruptedException {
        lock.lock();
        try {
            while (isFull()) {
                // 向缓冲区放入元素时,缓冲区已满,需等待
                notFull.await();
            }
            doPut(v);
            // 放入元素后,唤醒等待缓冲区非空的任意一条线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public V take() throws InterruptedExecption {
        lock.lock();
        try {
            while (isEmpty()) {
                // 从缓冲区取出元素时,缓冲区已空,需等待
                notEmpty.await();
            }
            V v = doTake();
            // 取出元素后,唤醒等待缓冲区非满的任意一条线程
            notFull.signal();
            return v; 
        } finally {
            lock.unlock();
        }
    }
}

唤醒方法的选择

只有同时满足以下两个条件时,才能用notifysingal

  • 条件队列中所有线程等待的条件相同,且从wait方法返回后执行的操作相同
  • 负责唤醒的线程在唤醒前所提供的条件,只够一条线程使用

参考文档

  • Brain Goetz等,Java并发编程实战(Java Concurrency in Practice),机械工业出版社,2012:243-244,247