我先开多个线程处理数据,最后对这多个线程处理的结果,由一个线程进行汇总处理。那么有一个问题就是,最后这个线程要等前面所有的线程都数据处理完成(每个线程处理的时间各不相同,结束的时间各不相同),这个线程 才开始进行处理数据。 为了处理这个解决这个线程的问题,Java语言为我们提供了集中语言上面的机制来解决,其中有CountDownLatch类、join函数、CylicBarrier类等…… 下面我将会对CountDownLatch类和join函数这两种就最具有典型对比意义的机制进行讲解与说明的使用方法、联系与区别,CylicBarrier类的使用会在后续给出。
CountDownLatch类使用 CountDownLatch类,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
类中主要方 法
public CountDownLatch(int count);--- 减少多少个计数public void countDown();-- -减少1个计数public void await() throws InterruptedException---一直等待,直到计数器被减到0位置,后面 的线程才能执行public void await(timeout,unit)throws InterruptedException---等待时间(时间数目、时间单位 时|分|秒)
方法介绍完了,直接上代码了。
import java.util.Random;import java.util.concurrent.CountDownLatch;public class CountDownLatchMain{ public static final int THREAD_NUMB = 3;//线程个数 public static void main(String[] args) throws Throwable { CountDownLatch l = new CountDownLatch(THREAD_NUMB); for (int i = 0; i < THREAD_NUMB; i++) { new Thread(new CountDownLatchTask(l, String.valueOf(i))). start (); } l.await();//主线程等待,直到所有的线程执行完成,后面的才进行执行 System.out.println("所有线程任务都结束了,后面的任务才开始……"); Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("任务结束了"); }}class CountDownLatchTask implements Runnable{ private CountDownLatch latch; private String threadname; public CountDownLatchTask(CountDownLatch latch, String threadname) { this.latch = latch; this.threadname = threadname; } public void run() { try { System.out.println("线程" + this.threadname + "任务开始……"); Thread.sleep(new Random().nextInt(3) * 1000); System.out.println("线程" + this.threadname + "任务结束……"); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (latch != null) { latch.countDown();//内部计算器减一 } } }}
join函数使用
在java线程Thread类中的join函数同样具有和CountDownLatch类同样的效果来实现上述代码的功能。废话不多说,直接上代码。两个代码非常相似,惊呆了~
import java.util.ArrayList;import java.util.List;import java.util.Random;public class CountDownLatchMain{ public static final int THREAD_NUMB = 3; public static void main(String[] args) throws Throwable { ListtList = new ArrayList<>(); for (int i = 0; i < THREAD_NUMB; i++) { Thread t = new Thread(new CountDownLatchTask(String.valueOf(i))); t.start(); tList.add(t); } for (Thread t : tList) { t.join();//这个等待其他线程执行完成后,然后在执行本线程后的任务 } System.out.println("所有线程任务都结束了,后面的任务才开始……"); Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("任务结束了"); }}class CountDownLatchTask implements Runnable{ private String threadname; public CountDownLatchTask(String threadname) { super(); this.threadname = threadname; } public void run() { try { System.out.println("线程" + this.threadname + "任务开始……"); Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("线程" + this.threadname + "任务结束……"); } catch (InterruptedException e) { e.printStackTrace(); } }}
CountDownLatch类与join函数的联系与区别
前面的部分谈了他们使用,下面来谈他们间的联系与区别.
联系:
他们都能够实现本文在前言中提到的场景,并且在前面使用的时候,代码也体现出来了。
区别:
场景一
在下面这个场景中CountDownLatch类比join函数实现更好: 例如:前面多个并发的线程中的任务分为两个阶段,每一个线程在第一个阶段的任务执行完成后,汇总的线程就只需要对前面所有线程第一阶段的数据进行处理,而并不关心前面并发线程的第二阶段的任务,在当前场景下join函数必须要等待前面所有的线程都执行完了后面的线程,才能执行,而CountDownLatch类实现效果更好。 代码如下
import java.util.Random;import java.util.concurrent.CountDownLatch;public class CountDownLatchMain{ public static final int THREAD_NUMB = 3;// 线程个数 public static void main(String[] args) throws Throwable { CountDownLatch l = new CountDownLatch(THREAD_NUMB); for (int i = 0; i < THREAD_NUMB; i++) { new Thread(new CountDownLatchTask(l, String.valueOf(i))).start(); } l.await();// 主线程等待,直到所有的线程执行完成,后面的才进行执行 System.out.println("所有线程 1阶段 任务都结束了,后面的任务才开始……"); Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("任务结束了"); }}class CountDownLatchTask implements Runnable{ private CountDownLatch latch; private String threadname; public CountDownLatchTask(CountDownLatch latch, String threadname) { this.latch = latch; this.threadname = threadname; } public void run() { try { System.out.println("线程" + this.threadname + " 1阶段---任务开始……"); Thread.sleep(new Random().nextInt(3) * 1000); System.out.println("线程" + this.threadname + " 1阶段---任务结束……"); latch.countDown();// 内部计算器减一 System.out.println("线程" + this.threadname + " 2阶段---任务开始……"); Thread.sleep(new Random().nextInt(20) * 1000); System.out.println("线程" + this.threadname + " 2阶段---任务结束……"); } catch (InterruptedException e) { e.printStackTrace(); } }}
场景二
当前面执行的并发任务在线程池中执行时,想要做到前面所有的并发任务都完成后,然后再执行后面的操作,join函数就显得无能为力,而CountDownLatch类则可以解决问题。 上代码:
import java.util.Random;import java.util.concurrent.CountDownLatch;import java.util.concurrent.Executor;import java.util.concurrent.Executors;public class CountDownLatchMain{ public static final int THREAD_NUMB = 3;// 线程个数 public static void main(String[] args) throws Throwable { CountDownLatch l = new CountDownLatch(THREAD_NUMB); Executor executor = Executors.newFixedThreadPool(4);// 固定线程池 for (int i = 0; i < THREAD_NUMB; i++) { executor.execute(new CountDownLatchTask(l, String.valueOf(i))); } l.await();// 主线程等待,直到所有的线程执行完成,后面的才进行执行 System.out.println("所有线程任务都结束了,后面的任务才开始……"); Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("任务结束了"); }}class CountDownLatchTask implements Runnable{ private CountDownLatch latch; private String threadname; public CountDownLatchTask(CountDownLatch latch, String threadname) { this.latch = latch; this.threadname = threadname; } public void run() { try { System.out.println("线程" + this.threadname + "任务开始……"); Thread.sleep(new Random().nextInt(3) * 1000); System.out.println("线程" + this.threadname + "任务结束……"); latch.countDown();// 内部计算器减一 } catch (InterruptedException e) { e.printStackTrace(); } }}
场景三
CountDownLatch对象中的countDown()函数可以传入参数,让内部计数器提前到达0,这样后面等待的线程可以提前执行,而不必等待剩下的线程执行完成才执行。 latch.countDown(3);// 内部计算器减三 这种场景有存在的可能,看需求……
总结 CountDownLatch类、join函数两种机制,都能起到一个或几个线程等待前面并发的线程执行完成后,在执行的功能,但是有时候在特殊场景下CountDownLatch类会有更好的用。