ReentrantLock使用Condition实现线程的等待和唤醒

发布时间:2022-03-01 11:09:58 作者:yexindonglai@163.com 阅读(550)

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同步块内才可以调用,

  1. synchronized (flag){
  2. flag.notify(); // 唤醒其中一个线程
  3. flag.wait();//将当前线程挂起
  4. }

和synchronized一样,condition也是只能在lock()方法unlock()方法的中间使用

  1. lock.lock();
  2. condition.signal(); // 唤醒等待的线程
  3. condition.await();// 让线程进入等待状态
  4. 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

  1. package com.Lock;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.Lock;
  4. import java.util.concurrent.locks.ReentrantLock;
  5. /**
  6. * 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用Condition实现
  7. */
  8. public class ConditionTest {
  9. public static void main(String[] args) {
  10. Lock lock = new ReentrantLock();
  11. Condition condition1 = lock.newCondition();
  12. Condition condition2 = lock.newCondition();
  13. Condition condition3 = lock.newCondition();
  14. ConditionTest test = new ConditionTest();
  15. // 开启线程A-----------------------------
  16. test.newThread(lock,"A",condition2,condition1);
  17. // 开启线程B-----------------------------
  18. test.newThread(lock,"B",condition3,condition2);
  19. // 开启线程C-----------------------------
  20. test.newThread(lock,"C",condition1,condition3);
  21. }
  22. /**
  23. * 开启新线程
  24. * @param name 线程名称
  25. * @param signalCondition 唤醒的线程
  26. * @param awaitCondition 等待的线程
  27. */
  28. public void newThread(Lock lock,String name,Condition signalCondition,Condition awaitCondition){
  29. new Thread(() -> {
  30. for (; ; ) {
  31. lock.lock();
  32. System.out.println(Thread.currentThread().getName());
  33. try {
  34. signalCondition.signal(); // 唤醒等待的线程
  35. awaitCondition.await();// 让线程进入等待状态
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. lock.unlock();
  40. }
  41. }, name).start();
  42. }
  43. }

另一个实现方式(与Condition无关)

当然,不用condition也可以实现,使用 ReentrantLock 的公平锁即可

  1. package com.Lock;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.Lock;
  4. import java.util.concurrent.locks.ReentrantLock;
  5. /**
  6. * 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用公平锁实现
  7. */
  8. public class ConditionTest_1 {
  9. public static void main(String[] args) {
  10. // 构造函数的参数设为true表示公平锁
  11. Lock lock = new ReentrantLock(true);
  12. ConditionTest_1 test = new ConditionTest_1();
  13. // 开启线程A-----------------------------
  14. test.newThread(lock,"A");
  15. // 开启线程B-----------------------------
  16. test.newThread(lock,"B");
  17. // 开启线程C-----------------------------
  18. test.newThread(lock,"C");
  19. }
  20. /**
  21. * 开启新线程
  22. * @param name 线程名称
  23. */
  24. public void newThread(Lock lock,String name){
  25. new Thread(() -> {
  26. for (; ; ) {
  27. lock.lock();
  28. System.out.println(Thread.currentThread().getName());
  29. lock.unlock();
  30. }
  31. }, name).start();
  32. }
  33. }

关键字Java