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();
- }
- }