Java 并发包之CountDownLatch

前言

CountDownLatch适用场景

  • 一个主任务由多个子任务组成
  • 主任务需要等待子任务完成才能继续执行

在正常情况下,主线程是不会等待子线程的

  • 代码
class CountDownLatchT2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            try {
                Thread.sleep(2000);//线程睡眠
                System.out.println("hello world");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        IntStream.range(0, 4).forEach(i -> new Thread(runnable).start());//启动4个线程
        System.out.println("子线程启动完毕");
        System.out.println("主线程执行完毕");
     }
}
复制代码

image.png

使用CountDownLatch

public class CountDownLatchT {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(4);//创建CountDownLatch实例,参数一般为子线程数量
        Runnable runnable = ()-> {
            try {
                Thread.sleep(2000);
                System.out.println("hello world");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                countDownLatch.countDown();//子线程运行完线程数减1直到 0
            }
        };
        IntStream.range(0,4).forEach(i ->new Thread(runnable).start());
        System.out.println("子线程启动完毕");
        countDownLatch.await();//主线程等待
        System.out.println("主线程执行完毕");
    }
}
复制代码

image.png

CountDownLatch大致流程和原理

代码含义

image.png

CountDownLatch源码

image.png

  • 一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
  • CountDownLatch使用给定的计数进行初始化。 由于调用了countDown方法, await方法会阻塞直到当前计数达到零,然后释放所有等待线程,并且任何后续的await调用都会立即返回。 这是一种一次性现象——计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier

image.png

await()方法

image.png

  • 使当前线程等待直到闩锁(latch)倒计时为零,除非线程被中断。
  • 如果当前计数为零,则此方法立即返回。
  • 如果当前计数大于零,则当前线程出于线程调度目的而被禁用并处于休眠状态,直到发生以下两种情况之一:
    • 由于调用了countDown方法,计数达到零; 或者
    • 一些其他线程中断当前线程。
  • 如果当前线程:
  • 在进入此方法时设置其中断状态; 或者
    • 等待时被打断,

    然后抛出InterruptedException并清除当前线程的中断状态。

countDown()方法

image.png

  • 递减锁存器的计数,如果计数达到零,则释放所有等待的线程。
  • 如果当前计数大于零,则递减。 如果新计数为零,则为线程调度目的重新启用所有等待线程。
  • 如果当前计数为零,则什么也不会发生。
  • countDown()大致原理如下:

image.png

await(long timeout, TimeUnit unit)方法

image.png

  • 使当前线程等待直到闩锁倒计时为零,除非线程被中断,或者指定的等待时间已过。
  • 如果当前计数为零,则此方法立即返回值为true 。
  • 如果当前计数大于零,则当前线程出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一:
    • 由于调用了countDown方法,计数达到零; 或者
    • 其他一些线程中断当前线程; 或者
    • 指定的等待时间已过。
  • 如果计数达到零,则该方法返回值为true 。
  • 如果当前线程:
    • 在进入此方法时设置其中断状态; 或者
    • 等待时被打断,

    然后抛出InterruptedException并清除当前线程的中断状态。

  • 如果指定的等待时间过去,则返回值false 。 如果时间小于或等于零,则该方法根本不会等待。

参数:
timeout – 等待的最长时间
unit – timeout参数的时间单位
返回值:
如果计数达到零,则为true如果在计数达到零之前经过了等待时间,则为false

该方法可以防止主线程(主任务)无限等待

样例(出现无限等待的情况)

public class CountDownLatchT {
    public static void main(String[] args) throws InterruptedException {
        //创建CountDownLatch实例,参数一般为子线程数量
        CountDownLatch countDownLatch = new CountDownLatch(5);
        Runnable runnable = ()-> {
            try {
                Thread.sleep(2000);
                System.out.println("hello world");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                countDownLatch.countDown();//子线程运行完线程数 -1直到 0
            }
        };
        IntStream.range(0,4).forEach(i ->new Thread(runnable).start());
        System.out.println("子线程启动完毕");
        countDownLatch.await();//主线程等待
        System.out.println("主线程执行完毕");
    }
}
复制代码

image.png
样例(使用await(long timeout, TimeUnit unit)

public class CountDownLatchT {
    public static void main(String[] args) throws InterruptedException {
        //创建CountDownLatch实例,参数一般为子线程数量
        CountDownLatch countDownLatch = new CountDownLatch(5);
        Runnable runnable = ()-> {
            try {
                Thread.sleep(2000);
                System.out.println("hello world");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                countDownLatch.countDown();//子线程运行完线程数 -1直到 0
            }
        };
        IntStream.range(0,4).forEach(i ->new Thread(runnable).start());
        System.out.println("子线程启动完毕");
        countDownLatch.await(2001, TimeUnit.MILLISECONDS);//主线程等待
        System.out.println("主线程执行完毕");
    }
}
复制代码

image.png

主线程就不会无限等待了

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享