设为首页 加入收藏

TOP

Java并发工具CountDownLatch的使用和原理(一)
2023-07-26 08:17:45 】 浏览:107
Tags:Java CountDownLatch

1.等待多线程完成的 CountDownLatch

  CountDownLatch 允许一个或多个线程等待其他线程完成操作。

  假如有这样一个需求:我们需要解析一个 Excel 里多个 sheet 的数据,此时可以考虑使用多线程,每个线程解析一个 sheet 里的数据,等到所有的 sheet 都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成 sheet 的解析操作,最简单的做法是使用 join()方法,如代码清单 1-1 所示。

代码清单1-1 CountDownLatchUseCase.java
public class CountDownLatchUseCase {
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(()->{
            System.out.println("parser1 finish");
        },"threadA");

        Thread threadB = new Thread(()->{
            System.out.println("parser2 finish");
        },"threadB");

        // 任务A B 开始执行
        threadA.start();
        threadB.start();

        // 等待AB 完成任务
        threadA.join();
        threadB.join();
    }
}

  join 用于让当前执行线程等待 join 线程执行结束。其实现原理是不停检查 join 线程是否存活,如果 join 线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去,代码片段如下。

java.lang.Thread#join(long)
// 同步方法
public final synchronized void join(long millis)
throws InterruptedException {
	long base = System.currentTimeMillis();
	long now = 0;

	if (millis < 0) {
		throw new IllegalArgumentException("timeout value is negative");
	}
	if (millis == 0) {
        // isAlive()方法返回一个boolean值,如果线程已经启动且尚未终止,则返回true;否则,返回false。此次的while循环为了防止被阻塞的线程被意外唤醒,走出这段循环代表线程任务已经执行完毕,线程已经终止。
		while (isAlive()) {
            // 线程进入等待状态
			wait(0);
		}
	} else {
        // 使用join(long millis)参数不为0走这部分逻辑,如果线程没有即时被唤醒,超时时间过后自己进入RUNNABLE状态。
		while (isAlive()) {
			long delay = millis - now;
			if (delay <= 0) {
				break;
			}
			wait(delay);
			now = System.currentTimeMillis() - base;
		}
	}
}

  直到 join 线程中止后,线程的 this.notifyAll()方法会被调用,调用 notifyAll()方法是在 JVM 里实现的,所以在 JDK 里看不到。
  在 JDK 1.5 之后的并发包中提供的 CountDownLatch 也可以实现 join 的功能,并且比join 的功能更多,将代码1-1使用CountDownLatch改下下,如代码清单 1-2 所示。

代码清单1-2 CountDownLatchUseCase.java
public class CountDownLatchUseCase {
    private static CountDownLatch countDownLatch = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(()->{
            System.out.println("parser1 finish");
            countDownLatch.countDown();
        },"threadA");

        Thread threadB = new Thread(()->{
            System.out.println("parser2 finish");
            countDownLatch.countDown();
        },"threadB");
        threadA.start();
        threadB.start();
        // 等待任务完成
        countDownLatch.await();
    }
}

  CountDownLatch 的构造函数接收一个 int 类型的参数作为计数器,如果你想等待 N个点完成,这里就传入 N。当我们调用 CountDownLatch 的 countDown 方法时,N 就会减 1,CountDownLatch 的 await 方法会阻塞当前线程,直到 N 变成零。由于 countDown方法可以用在任何地方,所以这里说的 N 个点,可以是 N 个线程,也可以是 1 个线程里的 N 个执行步骤。用在多个线程时,只需要把这个 CountDownLatch 的引用传递到线程里即可。

  如果有某个解析 sheet 的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使用另外一个带指定时间的 await 方法——await(long time,TimeUnit unit),这个方法等待特定时间后,就会不再阻塞当前线程。join 也有类似的方法。

计数器必须大于等于 0,只是等于 0 时候,计数器就是零,调用 await 方法时不会阻塞当前线程。CountDownLatch 不可能重新初始化或者修改 CountDownLatch对象的内部计数器的值。一个线程调用 countDown 方法 happen-before,另外一个线程调用 await 方法。

2.CountDownLatch原理解析

2.1 构造方法 public CountDownLatch(int count)

初始化好AQS同步状态为state

// 1.方法 CountDownLatch countDownLatch = new CountDownLatch(2);
# java.util.concurrent.CountDownLatch#CountDownLatch
// 使用构造方法将AQS的同步状态位初始为count
public CountDownLatch(int count) {
	if (count < 0) throw new IllegalArgumentException("count < 0");
	this.sync = new Sync(count);
}

Sync(int count) {
	setState(count);
}

2.2 方法public void

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇java 拦截、过滤器2 下一篇Java Maven Settings配置参考

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目