ReentrantLock通过Condition能够更加精细的控制多线程的休眠与唤醒。

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能够更加精细的控制多线程的休眠与唤醒。

参考:

Java技术——ReentrantLock的Condition的作用以及使用_reentrantlock.newcondition_SEU_Calvin的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值