synchronized 能完成大部分互斥操作。如果需要等待锁时响应中断、设置超时时间,或者区分多个等待条件,可以使用 ReentrantLock。

获取和释放

最基本的写法是先 lock(),然后在 try 中操作共享数据,在 finally 中 unlock()。获取失败时不能释放没有拿到的锁。

lockInterruptibly() 允许在等待获取锁时响应中断;tryLock() 立即返回是否成功,带时间参数的版本则等待一段时间。它们适合不同的失败处理方式,不是单纯的方法名称区别。

默认创建的是非公平锁,new ReentrantLock(true) 使用公平策略。不过不带超时的 tryLock() 即使在公平锁上也允许抢占,不能认为所有入口都严格排队。

用 Condition 等待条件

下面的盒子可以存放一个值,空时消费者等待,满时生产者等待:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionBoxDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    private Integer value;

    public void put(int next) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (value != null) notFull.await();
            value = next;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (value == null) notEmpty.await();
            int result = value;
            value = null;
            notFull.signal();
            return result;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionBoxDemo box = new ConditionBoxDemo();
        Thread consumer = new Thread(() -> {
            try {
                System.out.println(box.take());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        consumer.start();
        box.put(42);
        consumer.join();
    }
}

输出 42。两个 Condition 分别等待“有数据”和“有空位”,都关联同一把锁。

await() 会释放锁,返回前重新获取。signal() 把等待者转入后续的锁竞争,不意味着它立即开始执行,也不会替当前线程释放锁。

这里仍然必须用 while 检查条件。一方面要处理虚假唤醒,另一方面,线程真正重新取得锁时,其他线程可能已经先一步消费数据。

重入次数也要对应

同一线程可以重复 lock(),每次成功获取都要有一次对应的 unlock()。只释放一次,锁仍然可能被自己持有。

ReentrantLock 需要手动释放,所以这里的 finally 不能省略。如果只需要普通互斥,synchronized 的写法会简单一些。

最后修改:2018 年 02 月 27 日
如果觉得我的文章对你有用,请随意赞赏