ReentrantLock使用Condition实现线程的等待和唤醒
Posted java叶新东老师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantLock使用Condition实现线程的等待和唤醒相关的知识,希望对你有一定的参考价值。
ReentrantLock
ReentrantLock的出现是为了替代synchronized,因为使用lock锁之后代码会更加简洁,增加易读性,但是在jdk1.6之后,synchronized增加了一个锁升级的概念,所以从jdk1.6开始,都优先使用synchronized,这不代表着ReentrantLock就要废弃了,synchronized 是一个非公平锁,若要实现公平锁就得使用ReentrantLock;所以啊,没有万能的工具,但可以通过场景的不同而选择最适合的工具来使用;
Condition
Condition是为了控制线程的挂起和唤醒而发明的;也就是用来实现线程之间通讯使用的接口,每个condition里面维护了一个等待队列,与synchronized不同的是,synchronized锁对象关联的监视器对象仅有一个,所以等待队列也只有一个。
而一个ReentrantLock可以有多个Condition,这样可以根据不同的业务需求,在使用同一个lock锁对象的基础上使用多个等待队列,让不同性质的线程加入到不同的等待队列当中。
使用
众所周知,Object有wait()和notify()方法,用于线程间的通信。并且这两个方法只能在synchronized同步块内才可以调用,
synchronized (flag){
flag.notify(); // 唤醒其中一个线程
flag.wait();//将当前线程挂起
}
和synchronized一样,condition也是只能在lock()方法和unlock()方法的中间使用
lock.lock();
condition.signal(); // 唤醒等待的线程
condition.await();// 让线程进入等待状态
lock.unlock();
condition的常用方法如下
为什么要使用多个Condition
如果你只有生产者和消费的2个线程在交替执行的话, 只用一个condition就够了, 但是我们在实际开发过程中,线程的数量可不都只有2个,有可能是几十上百个的线程;假如有100个线程,包含了生产者和消费者的线程,你每次唤醒线程就得使用signalAll()方法了,你想想有100个线程那么多,你每次只唤醒一个线程,就会导致其他的线程一直在等待,会出现饿死的情况;但是你调用signalAll()方法之后虽然唤醒了其他的99个线程, 但是因为加了锁,所以只有一个线程会抢到锁,但是你有那么多线程竞争抢锁,在高并发情况下,这个上下文切换的竞争锁开销是非常大的;
并且,这99个争抢的线程中包含了生产者和消费者,如果每次都让生产者抢到了锁资源,就会导致生产了过多的产品无法消费的情况,就像工厂里面生产了超量了产品,这些产品都积压在仓库里面,卖不出去;这是一个很大的问题;
所以在这边引入了多个Condition 可以使得线程的控制粒度更精细;
实战
上面说到使用多个Condition 可以使得线程的控制粒度更精细;那么是怎么个精细法呢?接下来我们来模拟一下,使用 condition 实现三个(或者三个以上)线程打印ABC ABC.....;这样保证了每个线程都能执行到;不会出现饿死的情况,因为让线程按照自己想要的顺序执行, 所以也不会出现一直被生产者抢到的情况;
实现方式的思路是这样的:
1、启动的时候A线程先执行,
2、然后A线程挂起,启动B线程,
3、B线程挂起,启动C线程
4、C线程挂起,在启动A线程;
以上的方式就相当于启动了一个轮询,每个线程执行完成后调用下一个线程;
ConditionTest.java
package com.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用Condition实现
*/
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
ConditionTest test = new ConditionTest();
// 开启线程A-----------------------------
test.newThread(lock,"A",condition2,condition1);
// 开启线程B-----------------------------
test.newThread(lock,"B",condition3,condition2);
// 开启线程C-----------------------------
test.newThread(lock,"C",condition1,condition3);
}
/**
* 开启新线程
* @param name 线程名称
* @param signalCondition 唤醒的线程
* @param awaitCondition 等待的线程
*/
public void newThread(Lock lock,String name,Condition signalCondition,Condition awaitCondition){
new Thread(() -> {
for (; ; ) {
lock.lock();
System.out.println(Thread.currentThread().getName());
try {
signalCondition.signal(); // 唤醒等待的线程
awaitCondition.await();// 让线程进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}, name).start();
}
}
另一个实现方式(与Condition无关)
当然,不用condition也可以实现,使用 ReentrantLock 的公平锁即可
package com.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用公平锁实现
*/
public class ConditionTest_1 {
public static void main(String[] args) {
// 构造函数的参数设为true表示公平锁
Lock lock = new ReentrantLock(true);
ConditionTest_1 test = new ConditionTest_1();
// 开启线程A-----------------------------
test.newThread(lock,"A");
// 开启线程B-----------------------------
test.newThread(lock,"B");
// 开启线程C-----------------------------
test.newThread(lock,"C");
}
/**
* 开启新线程
* @param name 线程名称
*/
public void newThread(Lock lock,String name){
new Thread(() -> {
for (; ; ) {
lock.lock();
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
}, name).start();
}
}
以上是关于ReentrantLock使用Condition实现线程的等待和唤醒的主要内容,如果未能解决你的问题,请参考以下文章
从使用角度看 ReentrantLock 和 Condition
synchronizedLock接口Condition接口读写锁及ReentrantLock(重入锁) 特性及使用
Java多线程11:ReentrantLock的使用和Condition