0. 前言
之前知道 ReentrantLock 类有一个 newCondition(),用于获取 Lock 上的一个条件,还可以多次 newCondition() 获得多个条件,Condition 可用于线程间通信。是对比 ReentrantLock 和 Synchronized 关键字的区别时学习到的。但是有次面试被问到有没有用到过 ReentrantLock 的 Condition,瞬间懵逼了。所以搜集了些资料,通过一个小例子浅析一下 ReentrantLock 的 Condition。总结出的结果就是:
(1)通过 Condition 能够更加精细的控制多线程的休眠与唤醒。
(2)对于一个锁,我们可以为多个线程间建立不同的 Condition。
1. 使用 Condition 实现一个 ArrayBlockingQueue
我们将实现的 MyArrayBlockingQueue 类需要包括以下功能:
(1)如果一个线程调用该类的 take() 获取元素时,若集合为空则使调用线程阻塞。直到有其他线程为集合加入新元素。
(2)如果一个线程调用该类的 put() 添加新元素时,若集合满了则使调用线程阻塞。直到有其他线程从集合充 take 出数据。
1.1 内部成员以及构造方法
从下面源码中可以看出,我们使用了泛型,并且默认使用长度为 10 的数组来维护数据集合。定义了一个锁,并且根据锁的 lock.newCondition() 创建了两个条件,分别对应集合满和集合空两个条件。
- //维护的数据
- private final T[] datas;
- //数据的个数
- private int count;
- //插入取出的索引
- private int put_index;
- private int take_index;
- //锁
- private final Lock lock = new ReentrantLock();
- //定义两个条件,分别为"集合满"和"集合空"
- private Condition full = lock.newCondition();
- private Condition empty = lock.newCondition();
- //提供MyArrayBlockingQueue的构造方法,初始化T[]数据
- public MyArrayBlockingQueue() {
- this(10);
- }
- public MyArrayBlockingQueue(int maxSize) {
- this.datas = (T[]) new Object[maxSize];
- }
1.2 put/get 方法
- public void put(T data){
- lock.lock();
- try {
- if(count == datas.length){
- //此时集合已经满了
- System.out.println("集合已满,请等待...");
- //使调用线程挂起
- full.await();
- }
- //不满则添加新元素
- datas[put_index++] = data;
- count++;
- //此时唤醒等待取数据的线程
- empty.signalAll();
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- lock.unlock();
- }
- }
- public T take(){
- lock.lock();
- try {
- if(count == 0){
- //此时集合已经空了
- System.out.println("集合已空,请等待...");
- //使调用线程挂起
- empty.await();
- }
- //不空则取出最后一个数据
- take_index = count - 1;
- T result = datas[take_index--];
- count--;
- //此时唤醒等待写数据的线程
- full.signalAll();
- return result;
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- lock.unlock();
- }
- return null;
- }
put 方法中,如果集合满了,就调用 await() 方法使对应的线程释放锁,并且使调用线程阻塞。直到其他线程调用了 take() 方法,并调用了 full.signalAll() 时,该请求线程会被精准唤醒,重新竞争到锁后,代码继续往下执行。
若集合不满,则添加新元素,并且通过 empty.signalAll() 精准唤醒等待取数据的线程。
可以看到在 take 方法中也是类似的逻辑。这样就灵活并且方便的使用 Condition 完成了一个简单的线程安全的阻塞队列,一些角标等细节没有处理,毕竟主角是 Condition。
2. 总结
在 Condition 中,用 await() 替换 wait(),用 signal() 替换 notify(),用 signalAll() 替换 notifyAll(),传统线程的通信方式,Condition 都可以实现,这里注意,Condition 是被绑定到 Lock 上的,要创建一个 Lock 的 Condition 必须用 newCondition() 方法。Condition 的强大之处在于,对于一个锁,我们可以为多个线程间建立不同的 Condition。
如果采用 Object 类中的 wait(), notify(), notifyAll() 实现的话,当写入数据之后需要唤醒读线程时,不可能通过 notify() 或 notifyAll() 明确的指定唤醒读线程,而只能通过 notifyAll 唤醒所有线程,但是 notifyAll 无法区分唤醒的线程是读线程,还是写线程。所以,通过 Condition 能够更加精细的控制多线程的休眠与唤醒。
来源: http://blog.csdn.net/seu_calvin/article/details/70211712