JUC系列学习工具类CountDownLatch详解

作者:剑圣无痕 时间:2023-10-01 12:19:23 

前言:

项目中我们经常会遇到有时候需要等待其他线程完成任务后,主线程才能执行其他任务,那么我们将如何实现呢?

Join 解决方案

join 的工作原理是,检查thread是否存活,如果存活则让当前线程永远wait,直到 thread线程终止,线程的 notifyAll才会被调用。

具体实现

public class JoinAThread extends Thread
{
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName() +
               " 线程开始");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
        System.out.println( Thread.currentThread().getName() +
               " 线程执行完毕");
   }
}

public class JoinBThread extends Thread
{
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName() +
               " 线程开始");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
        System.out.println( Thread.currentThread().getName() +
               " 线程执行完毕");
   }
}

public class JoinTest
{
  public static void main(String[] args) throws InterruptedException
{
      JoinAThread joinA =new JoinAThread();
      Thread threadA =new Thread(joinA,"线程A");

JoinBThread joinB =new JoinBThread();
      Thread threadB =new Thread(joinB,"线程B");
      threadA.start();
      threadB.start();
      threadA.join();
      threadB.join();

System.out.println("子线程执行完成了,主线程"+Thread.currentThread().getName()+"开始执行了");
    }
}

执行结果

JUC系列学习工具类CountDownLatch详解

从结果中,我们可以看出只有子线程执行完成了,主线程才开始执行。join的实现我们需要每个线程进行join,如果存在多个线程,那么写起来会比较的繁琐,那么又没更新优化的方案了,答案是JUC下面的工具类CountDownLatch,也能完成同样的功能。

CountDownLatch 解决方案

具体实现

public class CountDownLatchTest
{
   private static Logger logger =LoggerFactory.getLogger(CountDownLatchTest.class);
   public static void main(String[] args) throws InterruptedException
   {
       ExecutorService exec = Executors.newCachedThreadPool();
       final CountDownLatch countDownLatch = new CountDownLatch(10);
       for (int i = 1; i <= 10; i++){
           exec.execute(() -> {
               try {
                   invokeServiec();
               } catch (InterruptedException e)
               {
                   logger.info("invoce service error",e);
               }
               finally
               {
                   //计数器减一
                   countDownLatch.countDown();
               }
           });
       }
       countDownLatch.await();
       logger.info("所有的子线程执行完成,主线程"+Thread.currentThread().getName()+"开始执行");
   }

private static void invokeServiec() throws InterruptedException
   {
       logger.info(Thread.currentThread().getName()+",开始执行任务");
       Thread.sleep(300);
   }
}

说明:CountDownLatch中有两个方法一个是await()方法,调用这个方法的线程会被阻塞,另外一个是countDown() 方法,调用此方法会使计数器减一,当计数器的值为0时,调用await()方法被阻塞的线程才会被唤醒。

执行结果:

JUC系列学习工具类CountDownLatch详解

原理说明

CountDownLatch 是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。

基本原理

CountDownLatch

CountDownLatch内部定义计数器和一个队列。当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器。

JUC系列学习工具类CountDownLatch详解

常用的方法

JUC系列学习工具类CountDownLatch详解

countDown:用于使计数器减一,其一般是执行任务的线程调用. await: 使用线程处于等待状态,其一般是主线程调用.

countDown

countDown实现方法如下:

JUC系列学习工具类CountDownLatch详解

说明:sync是一个AQS的队列,调用的为AQS的releaseShared方法,其具体实现如下:

JUC系列学习工具类CountDownLatch详解

而releaseShared调用为CountDownLatch中的内部类sync中的tryReleaseShared方法,具体实现如下:

JUC系列学习工具类CountDownLatch详解

tryReleaseShared(int)方法即对state属性进行减一操作的代码.通过CAS进行减操作来保证原子性,其会比较state是否为c,如果是则将其设置为nextc(自减1),如果state不为c,则说明有另外的线程在getState()方法和compareAndSetState()方法调用之间对state进行了设置,当前线程也就没有成功设置state属性的值,其会进入下一次循环中,如此往复,直至其成功设置state属性的值,即countDown()方法调用成功。

而doReleaseShared方法调用的为AbstractQueuedSynchronizer简称AQS的doReleaseShared方法,

JUC系列学习工具类CountDownLatch详解

说明:首先判断头结点不为空,且不为尾节点,说明等待队列中有等待唤醒的线程,这里需要说明的是,在等待队列中,头节点中并没有保存正在等待的线程,其只是一个空的Node对象,真正等待的线程是从头节点的下一个节点开始存放的,因而会有对头结点是否等于尾节点的判断。在判断等待队列中有正在等待的线程之后,其会清除头结点的状态信息,并且调用unparkSuccessor(Node)方法唤醒头结点的下一个节点,使其继续往下执行。如下是unparkSuccessor(Node)方法的具体实现:

JUC系列学习工具类CountDownLatch详解

可以看到,unparkSuccessor(Node)方法的作用是唤醒离传入节点最近的一个处于等待状态的线程,使其继续往下执行。

await

await方法实现如下:

JUC系列学习工具类CountDownLatch详解

await()方法调用了Sync对象的方法acquireSharedInterruptibly(int)方法,该方法的具体实现如下:

JUC系列学习工具类CountDownLatch详解

JUC系列学习工具类CountDownLatch详解

在doAcquireSharedInterruptibly(int)方法中,首先使用当前线程创建一个共享模式的节点。然后在一个for循环中判断当前线程是否获取到执行权限,如果有(r >= 0判断)则将当前节点设置为头节点,并且唤醒后续处于共享模式的节点;如果没有,则对调用shouldParkAfterFailedAcquire(Node, Node)和parkAndCheckInterrupt()方法使当前线程处于"搁置"状态,该"搁置"状态是由操作系统进行的,这样可以避免该线程无限循环而获取不到执行权限,造成资源浪费,这里也就是线程处于等待状态的位置,也就是说当线程被阻塞的时候就是阻塞在这个位置。当有多个线程调用await()方法而进入等待状态时,这几个线程都将等待在此处。

来源:https://juejin.cn/post/7132484030544478222

标签:JUC,工具类,CountDownLatch
0
投稿

猜你喜欢

  • java反射之方法反射的基本操作方法

    2021-11-26 00:45:36
  • Java优秀类库Hutool使用示例

    2021-12-09 18:12:06
  • Java map 优雅的元素遍历方式说明

    2022-11-12 16:57:28
  • springmvc之获取参数的方法(必看)

    2023-12-20 09:25:59
  • springboot使用redisRepository和redistemplate操作redis的过程解析

    2023-10-11 06:57:03
  • 手动添加jar包进Maven本地库内的方法

    2023-08-03 03:10:09
  • Springboot 异步任务和定时任务的异步处理

    2022-08-20 07:21:12
  • 基于Java8实现提高Excel读写效率

    2023-11-25 10:01:37
  • Android开发中应用程序分享功能实例

    2021-05-27 20:38:46
  • Android绘制验证码的实例代码

    2023-10-30 13:57:15
  • Java Exchanger并发类使用方法

    2023-08-19 20:20:41
  • C#中类的异常处理详解

    2023-02-21 17:25:01
  • JAVA中JSONObject对象和Map对象之间的相互转换

    2023-07-13 15:04:28
  • 基于C#实现屏幕取色器的示例详解

    2021-06-26 08:58:05
  • Apache Commons fileUpload实现文件上传之一

    2022-12-06 12:36:48
  • 关于C# dynamic装箱问题

    2021-09-13 19:22:40
  • Unity3D 计时器的实现代码(三种写法总结)

    2022-02-10 08:51:08
  • android球形水波百分比控件代码

    2021-06-20 06:03:34
  • IDEA打包jar-解决找不到或无法加载主类 main的问题

    2021-12-28 10:30:42
  • Android中获取资源 id 及资源 id 的动态获取

    2023-06-30 04:38:06
  • asp之家 软件编程 m.aspxhome.com