章节索引 :

锁机制之 Condition 接口

1. 前言

本节内容主要是对 Java 锁机制之 Condition 接口进行讲解,Condition 接口是配合 Lock 接口使用的,我们已经学习过 Lock 接口的相关知识,那么接下来对 Condition 接口进行讲解。本节内容的知识点如下:

  • Condition 接口简介,这是我们认识 Condition 接口的基础;
  • Condition 接口定义,整体上先了解 Condition 接口所包含的方法,为基础内容;
  • Condition 接口所提供的方法与 Object 所提供的方法的区别与联系,此部分为本节的重点之一;
  • Condition 对象的创建方式,这是开始使用 Condition 的前提,需要牢记;
  • Condition 接口的常用方法使用,这是本节课程的核心内容,掌握 Condition 接口使用方式,也是我们本节课程的最终目的所在;
  • 使用 ReentrantLock 与 Condition 接口,实现生产者与消费者模式。

2. Condition 接口简介

任意一个 Java 对象,都拥有一组监视器方法(定义在 java.lang.Object 上),主要包括 wait ()、wait (long timeout)、notify () 以及 notifyAll () 方法。这些方法与 synchronized 同步关键字配合,可以实现等待 / 通知模式。

定义:Condition 接口也提供了类似 Object 的监视器方法,与 Lock 配合可以实现等待 / 通知模式。Condition 可以看做是 Obejct 类的 wait ()、notify ()、notifyAll () 方法的替代品,与 Lock 配合使用。

3. Condition 接口定义

我们看到,从 JDK 的源码中可以获悉,Condition 接口包含了如下的方法,对于其中常用的方法,我们在后续的内容中会有比较详细的讲解。

public interface Condition {
    void await() throws InterruptedException;
    long awaitNanos(long nanosTimeout) throws InterruptedException; 
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

4. Condition 方法与 Object 方法的联系与区别

联系 1:都有一组类似的方法.

  • Object 对象监视器: Object.wait()、Object.wait(long timeout)、Object.notify()、Object.notifyAll()。
  • Condition 对象: Condition.await()、Condition.awaitNanos(long nanosTimeout)、Condition.signal()、Condition.signalAll()。

联系 2:都需要和锁进行关联。

  • Object 对象监视器: 需要进入 synchronized 语句块(进入对象监视器)才能调用对象监视器的方法。
  • Condition 对象: 需要和一个 Lock 绑定。

区别

  • Condition 拓展的语义方法,如 awaitUninterruptibly () 等待时忽略中断方法;
  • 在使用方法时,Object 对象监视器是进入 synchronized 语句块(进入对象监视器)后调用 Object.wait ()。而 Condition 对象需要和一个 Lock 绑定,并显示的调用 lock () 获取锁,然后调用 Condition.await ();
  • 从等待队列数量看,Object 对象监视器是 1 个。而 Condition 对象是多个。可以通过多次调用 lock.newCondition () 返回多个等待队列。

5. Condition 对象的创建

Condition 对象是由 Lock 对象创建出来的 (Lock.newCondition),换句话说,Condition 是依赖 Lock 对象的。那么我们来看看如果创建 Condition 对象。

此处仅提供示例代码,后续我们在进行方法讲解时,会有全部的代码示例,但在学习使用方法之前,我们必须先学会如何创建。

Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();

6. Condition 方法介绍

等待机制方法简介

  • void await() throws InterruptedException:当前线程进入等待状态,直到被其它线程的唤醒继续执行或被中断;
  • void awaitUninterruptibly():当前线程进入等待状态,直到被其它线程被唤醒;
  • long awaitNanos(long nanosTimeout) throws InterruptedException:当前线程进入等待状态,直到被其他线程唤醒或被中断,或者指定的等待时间结束;nanosTimeout 为超时时间,返回值 = 超时时间 - 实际消耗时间;
  • boolean await(long time, TimeUnit unit) throws InterruptedException:当前线程进入等待状态,直到被其他线程唤醒或被中断,或者指定的等待时间结束;与上个方法区别:可以自己设置时间单位,未超时被唤醒返回 true,超时则返回 false;
  • boolean awaitUntil(Date deadline) throws InterruptedException:当前线程等待状态,直到被其他线程唤醒或被中断,或者指定的截止时间结束,截止时间结束前被唤醒,返回 true,否则返回 false。

通知机制方法简介

  • void signal():唤醒一个线程;
  • void signalAll():唤醒所有线程。

7. ReentrantLock 与 Condition 实现生产者与消费者

非常熟悉的场景设计,这是我们在讲解生产者与消费者模型时使用的案例设计,那么此处有细微的修改如下,请学习者进行比照学习,印象更加深刻。

场景修改

  • 创建一个工厂类 ProductFactory,该类包含两个方法,produce 生产方法和 consume 消费方法(未改变);
  • 对于 produce 方法,当没有库存或者库存达到 10 时,停止生产。为了更便于观察结果,每生产一个产品,sleep 3000 毫秒(5000 变 3000,调用地址也改变了,具体看代码);
  • 对于 consume 方法,只要有库存就进行消费。为了更便于观察结果,每消费一个产品,sleep 5000 毫秒(sleep 调用地址改变了,具体看代码);
  • 库存使用 LinkedList 进行实现,此时 LinkedList 即共享数据内存(未改变);
  • 创建一个 Producer 生产者类,用于调用 ProductFactory 的 produce 方法。生产过程中,要对每个产品从 0 开始进行编号 (新增 sleep 3000ms);
  • 创建一个 Consumer 消费者类,用于调用 ProductFactory 的 consume 方法 (新增 sleep 5000ms);
  • 创建一个测试类,main 函数中创建 2 个生产者和 3 个消费者,运行程序进行结果观察(未改变)。

实例

public class DemoTest {
        public static void main(String[] args) {
            ProductFactory productFactory = new ProductFactory();
            new Thread(new Producer(productFactory),"1号生产者"). start();
            new Thread(new Producer(productFactory),"2号生产者"). start();
            new Thread(new Consumer(productFactory),"1号消费者"). start();
            new Thread(new Consumer(productFactory),"2号消费者"). start();
            new Thread(new Consumer(productFactory),"3号消费者"). start();
        }
}

class ProductFactory {
    private LinkedList<String> products; //根据需求定义库存,用 LinkedList 实现
    private int capacity = 10; // 根据需求:定义最大库存 10
    private Lock lock = new ReentrantLock(false);
    private Condition p = lock.newCondition();
    private Condition c = lock.newCondition();
    public ProductFactory() {
        products = new LinkedList<String>();
    }
    // 根据需求:produce 方法创建
    public void produce(String product) {
        try {
            lock.lock();
            while (capacity == products.size()) { //根据需求:如果达到 10 库存,停止生产
                try {
                    System.out.println("警告:线程("+Thread.currentThread().getName() + ")准备生产产品,但产品池已满");
                    p.await(); // 库存达到 10 ,生产线程进入 wait 状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            products.add(product); //如果没有到 10 库存,进行产品添加
            System.out.println("线程("+Thread.currentThread().getName() + ")生产了一件产品:" + product+";当前剩余商品"+products.size()+"个");
            c.signalAll(); //生产了产品,通知消费者线程从 wait 状态唤醒,进行消费
        } finally {
            lock.unlock();
        }
    }

    // 根据需求:consume 方法创建
    public String consume() {
        try {
            lock.lock();
            while (products.size()==0) { //根据需求:没有库存消费者进入wait状态
                try {
                    System.out.println("警告:线程("+Thread.currentThread().getName() + ")准备消费产品,但当前没有产品");
                    c.await(); //库存为 0 ,无法消费,进入 wait ,等待生产者线程唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            String product = products.remove(0) ; //如果有库存则消费,并移除消费掉的产品
            System.out.println("线程("+Thread.currentThread().getName() + ")消费了一件产品:" + product+";当前剩余商品"+products.size()+"个");
            p.signalAll();// 通知生产者继续生产
            return product;
        } finally {
            lock.unlock();
        }
    }
}

class Producer implements Runnable {
    private ProductFactory productFactory; //关联工厂类,调用 produce 方法
    public Producer(ProductFactory productFactory) {
        this.productFactory = productFactory;
    }
    public void run() {
        int i = 0 ; // 根据需求,对产品进行编号
        while (true) {
            productFactory.produce(String.valueOf(i)); //根据需求 ,调用 productFactory 的 produce 方法
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
    }
}
class Consumer implements Runnable {
    private ProductFactory productFactory;
    public Consumer(ProductFactory productFactory) {
        this.productFactory = productFactory;
    }
    public void run() {
        while (true) {
            productFactory.consume();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果验证

线程(1号生产者)生产了一件产品:0;当前剩余商品1个
线程(2号生产者)生产了一件产品:0;当前剩余商品2个
线程(1号消费者)消费了一件产品:0;当前剩余商品1个
线程(2号消费者)消费了一件产品:0;当前剩余商品0个
警告:线程(3号消费者)准备消费产品,但当前没有产品
线程(2号生产者)生产了一件产品:1;当前剩余商品1个
线程(1号生产者)生产了一件产品:1;当前剩余商品2个
线程(3号消费者)消费了一件产品:1;当前剩余商品1个
线程(2号消费者)消费了一件产品:1;当前剩余商品0个
警告:线程(1号消费者)准备消费产品,但当前没有产品

8. 小结

本节内容为主要对 Condition 接口进行了讲解,Condition 接口作为 Lock 接口的监视器,是非常重要的接口,我们需要非常重视 Condition 接口的学习。

本节内容最终的目的是使用 Condition 接口和 Lock 配合实现案例,核心内容即为 Condition 接口的使用,请翻看生产者与消费者一节,对比进行学习。