上篇介绍了Semaphore 和 CyclicBarrier,这次说一下另外两个:CountDownLatch 和 Exchanger:
【CountDownLatch 】
倒计数器!用这个类,可以很好的模拟一个运动会场景:3个运动员1个裁判,运动员准备好后,裁判发令,然后运动员开跑,运动员结束后,告知裁判,等所有运动员都返回终点,裁判宣布比赛结果:
package cn.test; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException, ExecutionException { // 裁判的计数器,等待所有运动员准备好后,发令开跑 final CountDownLatch refereeCountDownLatch = new CountDownLatch(1); // 运动员的计数器,一个3个运动员,每个运动员跑完后通过计数器报到,所有运动员都跑完后,裁判宣布成绩 final CountDownLatch athletesCountDownLatch = new CountDownLatch(3); // 创建3个线程的线程池,作为3个待跑的运动员 ExecutorService athletesThreadPool = Executors.newFixedThreadPool(3); // 因为我们最后去宣布成绩,所以我们希望运动员线程执行完毕后,返回一个结果,所以我们此处用CompletionService CompletionService<String> completionService = new ExecutorCompletionService<String>(athletesThreadPool); // 提交3个任务,作为3个运动员的跑步过程 for(int i=0;i<3;i++){ completionService.submit(new Callable<String>(){ @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName() + " 进入准备起跑阶段!"); // 通知裁判 refereeCountDownLatch.await(); // 开始起跑,随即睡一段时间,来模拟跑步过程 new Random().nextDouble()返回0-1之间的数字 long useTime = (long)(new Random().nextDouble() * 10000); Thread.sleep(useTime); System.out.println(Thread.currentThread().getName() + " 跑步结束!耗时:" + useTime); // 报告计数器,并且返回结果! athletesCountDownLatch.countDown(); return "运动员:" + Thread.currentThread().getName(); }}); } // 主线程模拟裁判,先等上2秒钟,让运动员准备就绪(这里可以改进为当运动员准备好后,通知裁判!) Thread.sleep(2000); // 裁判宣布比赛可以 System.out.println("裁判宣布比赛开始:"); refereeCountDownLatch.countDown(); // 裁判阻塞,等待结果 athletesCountDownLatch.await(); // 当所有运动员结束后,裁判宣布结果。CompletionService会按照执行完毕的顺序,依次返回线程执行结果 Future<String> firstAthlete = completionService.take(); Future<String> secondAthlete = completionService.take(); Future<String> thridAthlete = completionService.take(); System.out.println("本次运动会,冠军:" + firstAthlete.get()); System.out.println("本次运动会,亚军:" + secondAthlete.get()); System.out.println("本次运动会,季军:" + thridAthlete.get()); } }
比赛结果为:
pool-1-thread-1 进入准备起跑阶段! pool-1-thread-3 进入准备起跑阶段! pool-1-thread-2 进入准备起跑阶段! 裁判宣布比赛开始: pool-1-thread-3 跑步结束!耗时:8577 pool-1-thread-1 跑步结束!耗时:8686 pool-1-thread-2 跑步结束!耗时:9762 本次运动会,冠军:运动员:pool-1-thread-3 本次运动会,亚军:运动员:pool-1-thread-1 本次运动会,季军:运动员:pool-1-thread-2
创建CountDownLatch会接受一个整形参数,表示计数器初始值,调用对象的countDown()方法,会将这个初始值减1,直到0为止。线程调用对象的await()方法,如果计数器的值不为0,则阻塞,否则,该调用直接返回!我们上述过程,先让3个运动员线程全部阻塞在初始值为1的计数器上,裁判调用一下计数器的countDown()方法,3个运动员开始跑步。然后裁判await在一个初始值为3的计数器上,每个运动员结束后,调用一下这个计数器的countDown()方法,当所有运动员结束,裁判从计数器的await方法返回,宣布结果!这里我们还结合使用了ExecutorService 和 CompletionService,具体用法,可参考前面介绍!
【Exchanger】
线程变量交换器!这个可以实现两个线程在运行间,随时交换两个值!我们考虑一个例子,卖家和买家。卖家线程拿着货说我要卖,然后等待,买家线程来了,出钱,换得东西,两个线程继续进行:
package cn.test; import java.util.concurrent.Exchanger; public class ExchangerTest { public static void main(String[] args) { final Exchanger<String> exchanger = new Exchanger<String>(); // 先创建一个卖家线程,将东西准备好,开始卖 new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + " : 卖东西了.....一手交钱一手交货!"); try { String goods = "Good TV!"; String getBack = exchanger.exchange(goods); System.out.println(Thread.currentThread().getName() + " : " + goods + " 卖出去了,得到了 " + getBack); } catch (InterruptedException e) { e.printStackTrace(); } }}, "Seller").start(); // 再创建一个买家线程,将钱准备好,开始买 new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + " : 买东西了.....一手给货一手付钱!"); try { String money = "2000$"; String getBack = exchanger.exchange(money); System.out.println(Thread.currentThread().getName() + " : " + money + " 花了,得到了 " + getBack); } catch (InterruptedException e) { e.printStackTrace(); } }}, "Buyer").start(); } }
交易结果为:
Seller : 卖东西了.....一手交钱一手交货! Buyer : 买东西了.....一手给货一手付钱! Buyer : 2000$ 花了,得到了 Good TV! Seller : Good TV! 卖出去了,得到了 2000$
我们看到两个线程互相交互了各自的一个局部变量!创建Exchanger对象,第一个线程调用对象的exchange方法(传递一个参数,即用于交换的变量),阻塞,等待另一个线程在这个对象上同样调用这个exhange方法。第二个线程调用后,两者互换调用参数,该方法返回!Exchanger是一个支持泛型的类,泛型就是用于交换的值的类型!
以上就是并发包中提到的4个好玩的线程控制工具类!大家了解一下即可,如果在实际中有这方面的需求,可以考虑能不能用这几个好玩的工具类来简化自己的线程控制代码!