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

举报
IT 叶新东老师 发表于 2021/12/22 00:58:17 2021/12/22
2.4k+ 0 0
【摘要】 ReentrantLock     ReentrantLock的出现是为了替代synchronized,因为使用lock锁之后代码会更加简洁,增加易读性,但是在jdk1.6之后,synchronized增加了一个锁升级的概念,所以从jdk1.6开始,都优先使用synchronized,这不代表着Reent...

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

文章来源: yexindong.blog.csdn.net,作者:java叶新东老师,版权归原作者所有,如需转载,请联系作者。

原文链接:yexindong.blog.csdn.net/article/details/118460141

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。