IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 抽象队列同步器(AQS)之 CountDownLatch 的原理 -> 正文阅读

[Java知识库]抽象队列同步器(AQS)之 CountDownLatch 的原理

一、概述

在日常开发中,经常会遇到需要在主线程中开启多个线程去并发执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在 CountDownLatch 出现之前都是使用线程的 join() 方法来实现这一点,但是 join() 方法不够灵活,不能满足不同场景的需要,所以出现了 CountDownLatch 这个类。

二、使用示例

public class AQS_CountDownLatch_Demo {
    private static CountDownLatch countDownLatch = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " -- running");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }
        });

        executorService.submit(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " -- running");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }
        });

        System.out.println("等待所有子线程执行完毕");
        countDownLatch.await();
        executorService.shutdown();
    }
}
// 控制台输出:
等待所有子线程执行完毕
pool-1-thread-2 -- running
pool-1-thread-1 -- running

三、实现原理探究

1. 查看类图:

在这里插入图片描述
由上图可知,CountDownLatch 是使用 AQS 实现的,内部有个计数器 count,实际上把 count 的值赋给了 AQSstate,查看构造可知:

 private final Sync sync;
 
 public CountDownLatch(int count) {
     if (count < 0) throw new IllegalArgumentException("count < 0");
     this.sync = new Sync(count);
 }
 
Sync(int count) {
	setState(count);
}

2. 方法剖析

1)public void countDown()

线程调用该方法后,计数器的值将递减,如果计数器值为 0, 则唤醒所有因调用 await 方法而被阻塞的线程,否则什么都不做。

//java.util.concurrent.CountDownLatch#countDown
public void countDown() {
  sync.releaseShared(1);
 }

//java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
 public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
          doReleaseShared();
          return true;
      }
      return false;
  }


//java.util.concurrent.CountDownLatch.Sync#tryReleaseShared
// 循环进行 CAS ,直到当前线程成功完成 CAS 使计数器值(状态值 state)减1并更新到 state
  protected boolean tryReleaseShared(int releases) {
  // Decrement count; signal when transition to zero
      for (;;) {
          int c = getState();
          if (c == 0)
              return false;
          int nextc = c-1;
          if (compareAndSetState(c, nextc))
              return nextc == 0;
      }
  }

2)public void await() throws InterruptedException

当调用了 CountDownLatch 对象的 await 方法后,当前线程会被阻塞,直到触发下列情况之一:
1)当所有线程都调用了 CountDownLatchcountDown() ,也就是计数器的值为0;
2)其它线程调用了当前线程的 interrupt() 方法中断了当前线程,当前线程就会抛出 InterruptedException 异常,然后返回。
由下面代码可知,线程获取资源时是可以被中断的,并且获取的资源是共享资源。

// java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
 public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())// 如果线程被中断,则抛出异常
        throw new InterruptedException();
       // 当前计数器值是否为0,为0 则直接返回,否则进入 AQS 的队列等待
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}


//java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
// 检查当前计数器是否为0
 protected int tryAcquireShared(int acquires) {
     return (getState() == 0) ? 1 : -1;
 }

3) public boolean await(long timeout, TimeUnit unit)

当线程调用了 CountDownLatch 对象的该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:
1)当所有线程都调用了 CountDownLatch 对象的 countDown 方法,计数器的值为0时,这时候会返回 true;
2)设置的 timeout 时间到了,因超时而返回 false;
3)其它线程调用了当前线程的 interrupt() 方法中断了当前线程,当前线程就会抛出 InterruptedException 异常,然后返回。

public boolean await(long timeout, TimeUnit unit)
  throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}


//java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireSharedNanos
 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
      throws InterruptedException {
     if (Thread.interrupted())
         throw new InterruptedException();
     return tryAcquireShared(arg) >= 0 ||
         doAcquireSharedNanos(arg, nanosTimeout);
 }

//java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedNanos
  private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
      throws InterruptedException {
     if (nanosTimeout <= 0L)
         return false;
     final long deadline = System.nanoTime() + nanosTimeout;
     final Node node = addWaiter(Node.SHARED);
     boolean failed = true;
     try {
         for (;;) {
             final Node p = node.predecessor();
             if (p == head) {
                 int r = tryAcquireShared(arg);
                 if (r >= 0) {
                     setHeadAndPropagate(node, r);
                     p.next = null; // help GC
                     failed = false;
                     return true;
                 }
             }
             nanosTimeout = deadline - System.nanoTime();
             if (nanosTimeout <= 0L)
                 return false;
             if (shouldParkAfterFailedAcquire(p, node) &&
                 nanosTimeout > spinForTimeoutThreshold)
                 LockSupport.parkNanos(this, nanosTimeout);
             if (Thread.interrupted())
                 throw new InterruptedException();
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }

四、总结

CountDownLatch 相比使用 join() 来实现线程间同步,前者更有灵活性和方便性。CountDownLatch 是使用 AQS 实现的,使用AQS 的状态变量来存放计数器的值。首先在初始化 CountDownLatch 时设置状态值(计数器值),当多个线程调动 countDown() 时实际是原子性递减 AQS 的状态值。当调用 await() 后当前线程会被放入 AQS 的阻塞队列等待计数器为0 再返回。其它线程调用 countDown() 让计数器递减1,当计数器值变为0时,当前线程还要调用 AQSdoReleaseShared 来激活由于调用 await() 而被阻塞的线程。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:04:04  更:2022-04-06 16:05:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:42:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码