1.CountDownLatch工作原理

        CountDownLatch在多线程并发编程中充当一个计时器的功能,并且维护一个count的变量,并且其操作都是原子操作,该类主要通过countDown()和await()两个方法实现功能的,首先通过建立CountDownLatch对象,并且传入参数即为count初始值。如果一个线程调用了await()方法,那么这个线程便进入阻塞状态,并进入阻塞队列。如果一个线程调用了countDown()方法,则会使count-1;当count的值为0时,这时候阻塞队列中调用await()方法的线程便会逐个被唤醒,从而进入后续的操作。比如下面的例子就是有两个操作,一个是读操作一个是写操作,现在规定必须进行完写操作才能进行读操作。所以当最开始调用读操作时,需要用await()方法使其阻塞,当写操作结束时,则需要使count等于0。因此count的初始值可以定为写操作的记录数,这样便可以使得进行完写操作,然后进行读操作。

首先是创建实例 CountDownLatch countDown = new CountDownLatch(2)
需要同步的线程执行完之后,计数-1; countDown.countDown()
需要等待其他线程执行完毕之后,再运行的线程,调用 countDown.await()实现阻塞同步

2. 应用场景

前面给了一个demo演示如何用,那这个东西在实际的业务场景中是否会用到呢?

因为确实在一个业务场景中使用到了,不然也就不会单独捞出这一节...

电商的详情页,由众多的数据拼装组成,如可以分成一下几个模块

交易的收发货地址,销量
商品的基本信息(标题,图文详情之类的)
推荐的商品列表
评价的内容
....
上面的几个模块信息,都是从不同的服务获取信息,且彼此没啥关联;所以为了提高响应,完全可以做成并发获取数据,如

线程1获取交易相关数据
线程2获取商品基本信息
线程3获取推荐的信息
线程4获取评价信息
....
但是最终拼装数据并返回给前端,需要等到上面的所有信息都获取完毕之后,才能返回,这个场景就非常的适合 CountDownLatch来做了

在拼装完整数据的线程中调用 CountDownLatch#await(long, TimeUnit) 等待所有的模块信息返回
每个模块信息的获取,由一个独立的线程执行;执行完毕之后调用 CountDownLatch#countDown() 进行计数-1
 

3.代码演示

package cn.day13;
 
import java.util.concurrent.CountDownLatch;
 
public class Test {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final CountDownLatch latch = new CountDownLatch(2);
 
		new Thread() {
			public void run() {
				try {
					System.out.println("子线程" + Thread.currentThread().getName()
							+ "正在执行");
					Thread.sleep(3000);
					System.out.println("子线程" + Thread.currentThread().getName()
							+ "执行完毕");
					latch.countDown();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			};
		}.start();
 
		new Thread() {
			public void run() {
				try {
					System.out.println("子线程" + Thread.currentThread().getName()
							+ "正在执行");
					Thread.sleep(3000);
					System.out.println("子线程" + Thread.currentThread().getName()
							+ "执行完毕");
					latch.countDown();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			};
		}.start();
 
		try {
			System.out.println("等待2个子线程执行完毕...");
			latch.await();
			System.out.println("2个子线程已经执行完毕");
			System.out.println("继续执行主线程");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
 
}

运行结果:


  1. 子线程Thread-0正在执行
  2. 等待2个子线程执行完毕...
  3. 子线程Thread-1正在执行
  4. 子线程Thread-0执行完毕
  5. 子线程Thread-1执行完毕
  6. 2个子线程已经执行完毕
  7. 继续执行主线程

代码二

public class CountDownLatchDemo {
    private CountDownLatch countDownLatch;
 
    private int start = 10;
    private int mid = 100;
    private int end = 200;
 
    private volatile int tmpRes1, tmpRes2;
 
    private int add(int start, int end) {
        int sum = 0;
        for (int i = start; i <= end; i++) {
            sum += i;
        }
        return sum;
    }
 
 
    private int sum(int a, int b) {
        return a + b;
    }
 
    public void calculate() {
        countDownLatch = new CountDownLatch(2);
 
        Thread thread1 = new Thread(() -> {
            try {
                // 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + " : 开始执行");
                tmpRes1 = add(start, mid);
                System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + tmpRes1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }, "线程1");
 
        Thread thread2 = new Thread(() -> {
            try {
                // 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + " : 开始执行");
                tmpRes2 = add(mid + 1, end);
                System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + tmpRes2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }, "线程2");
 
 
        Thread thread3 = new Thread(()-> {
            try {
                System.out.println(Thread.currentThread().getName() + " : 开始执行");
                countDownLatch.await();
                int ans = sum(tmpRes1, tmpRes2);
                System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + ans);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程3");
 
        thread3.start();
        thread1.start();
        thread2.start();
    }
 
 
    public static void main(String[] args) throws InterruptedException {
        CountDownLatchDemo demo = new CountDownLatchDemo();
        demo.calculate();
 
        Thread.sleep(1000);
    }
}

 

运行结果



  1. 线程3 : 开始执行
  2. 线程1 : 开始执行
  3. 线程2 : 开始执行
  4. 线程1 : calculate ans: 5005
  5. 线程2 : calculate ans: 15050
  6. 线程3 : calculate ans: 20055