为什么80%的码农都做不了架构师?>>>
其他同步工具类
CyclicBarrier
接下来,我们再介绍一个java5当中的线程同步工具类。叫CyclicBarrier,循环的障碍物。可以理解为路障,大家在一些小区里面,那个路上都会修一些那个坡,那就叫路障,就是为了让车减速的那种,叫路障。这个Cyclic叫循环的,就是说,这个路障可以反复的使用。它可以实现什么样的功能呢?譬如说,我们公司要搞一个郊游的活动,我们约定,星期天上午9点在软件园集合,人到齐了,大家一起上车出发。好,大家每一个人就是一个线程吧,就陆陆续续的,就边走边玩,边聊边来,三三两两,就陆陆续续的就来到了这个软件园的楼底下,这个先来到的人就怎么办?就上车,然后就去?不是,他就停在这里,就阻塞了,他没办法,只能在这里玩了,只能在这里等着了,因为别人还没有到嘛。当我们约定的报名的员工全部到了,欸,一看,全部到了,最后一名员工到了。这时候,大家就又都可以继续向下一个目标迈进了。那就都上车,都走了嘛。都各干各的了嘛。然后大家在公园里面玩,玩,玩。然后我们说,12点钟,到12点钟要吃饭,那就陆陆续续的就都到食堂门口,有的先到,有的晚到,先到的怎么办?在那里等待,阻塞。最后一个到的就,一到就开始开饭。接着又可以,大家又可以继续各自往下向下了运行了。就是这样一种效果。
\ / \ | / ------------------------三个线程干完各自的任务,在不同的时刻到达集合点后,就可以接着忙各自的工作去了,再到达新的集合点,再去忙各自的工作, 到达集合点了用CyclicBarrier对象的await方法表示。 / | \ / | \ ------------------- 为什么几个人能碰到一起,说白了,就是大家都把手头这一阶段的工作做完了,就可以碰到一起了。譬如,我下楼等方老师,就是等他手头工作做完了,他到达了要集合的状态,就集合了。 |
下面看一个代码示例:
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class CyclicBarrierTest {
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CyclicBarrier cb = new CyclicBarrier(3);//约定,当等待的线程数达到3时,解除等待,继续运行 for (int i = 0; i < 3; i++) { Runnable runnable = new Runnable() { public void run() { try { Thread.sleep((long) (Math.random() * 10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点1,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候")); cb.await();
Thread.sleep((long) (Math.random() * 10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点2,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候")); cb.await(); Thread.sleep((long) (Math.random() * 10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候")); cb.await(); } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); } service.shutdown(); } } |
以上代码,创建一个CyclicBarrier对象,指定一个数字,表示要有几个线程同时到达,接下来,如果程序需要在某个地方停下来,等待别人,那么,就调用这个CyclicBarrier的await()方法,想在什么地方停,想在什么地方集合,就在什么地方await.
构造方法详细信息 |
public CyclicBarrier(int parties,
Runnable barrierAction)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。
参数:
parties
- 在启动 barrier 前必须调用 await()
的线程数
barrierAction
- 在启动 barrier 时执行的命令;如果不执行任何操作,则该参数为 null
抛出:
IllegalArgumentException - 如果 parties
小于 1
public CyclicBarrier(int parties)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
参数:
parties
- 在启动 barrier 前必须调用 await()
的线程数
抛出:
IllegalArgumentException - 如果 parties
小于 1
CountDownLatch
接下来,我们来看另外一个同步工具类。ConutDownLatch,它就相当于是一个倒计数的计数器。当计数器归到0的时候,正在等这个计数器的这些线程就开始活动。就好比这个香港回归,是不是有个倒计时器呀。等到那个倒计时器一到0的时候,那各个部门是不是开始行动,敲锣的敲锣,打鼓的大鼓,唱歌的唱歌,是不是?各个线程都行动起来了,大家都在等待这一个时刻,激动人心的时刻。就相当于是这么一个倒计时器。它刚开始有一个初始值,初始值等于N,然后呢,就会有其它的线程让这个N呢不停的向下减,减,减……,减到0的时候,其它程序都在等这个计时器的,立马就接到通知了,就开始继续向下运行了。就是这样的一个计时器效果。我们来看一下代码,立马就知道是怎么回事了:
注意,当一个或几个线程在等待计数器归0的时候,需要有另外一个线程来对计数器进行减的操作,否则计数器永远也不会归0.所以这个倒计数的计数器的使用需要两种功能的线程互相配合才能完成,一种线程里面等待计数器归0,另一种线程里面操作计数器递减来达到计数器归0.两者之间相互作用,相互配合。
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class CountdownLatchTest {
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CountDownLatch cdOrder = new CountDownLatch(1);//创建一个ConuntDownLatch对象,计数器的计数是1 final CountDownLatch cdAnswer = new CountDownLatch(3);//创建一个CountDownLatch对象,计数器的计数是3 for (int i = 0; i < 3; i++) { Runnable runnable = new Runnable() {//循环启动了3个线程 public void run() { try { System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令"); cdOrder.await();//等待cdOrder计数器达到0的状态 System.out.println("线程" + Thread.currentThread().getName() + "已接受命令"); Thread.sleep((long) (Math.random() * 10000)); System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果"); cdAnswer.countDown();//把cdAnswer这个计数器减1 } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); } try { Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令"); cdOrder.countDown();//把cdOrder计数器身上的计数减1 System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果"); cdAnswer.await();//等待cdAnswer计数器归0 System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果"); } catch (Exception e) { e.printStackTrace(); } service.shutdown();
} } |
这可以做这样一个模拟百米赛跑的游戏,裁判一声口哨,所有正在等待的运动员都跑,接着裁判就等待运动员们逐一的到达,当所有的运动员到达以后,裁判就可以继续向下走,就可以宣布比赛结束,开始公布成绩。用CountDownLatch这个技术模拟起来很方便。代码里面,我用了2个计数器,一个cdOrder计数器是用于吹口哨的,计数是1,吹一声,大家都开始跑。一个是等待结果的计数器cdAnswer,大家都到了,结果才出来。也就是,我们可以实现一个人等待其它的人来通知他,就是多个人来通知一个人。我们也可以实现一个人通知多个人。好比有一个文件,必须5个领导都签字了,才能继续向下处理,这不就5个领导,分别签上一笔,做后一个领导签完了,就让文件继续向下走。可以多个人来通知这件事,也可以一个人通知多个事。
Exchanger
那么接下来再介绍一个工具,叫Exchanger.这个工具有什么用呢?它可以实现两个线程之间的数据交换。两个线程之间的数据交换就好比电影里面一个卖毒粉的和一个买毒粉的要交换东西。买毒粉的用什么去交换什么?用钱交换毒粉。卖毒粉的用什么交换什么?用毒粉交换钱,是不是?大家约定说,我们在哪里去接头,结果那个买毒粉的先到了,怎么办?等。当那个买毒粉的到达了,怎么办?两个迅速交换,扭头就各忙各的去了。这个就可以实现这种,两者同时到达了以后才开始立马交换数据。这个很简单,我们用了一个小工具就可以正好解决这样一个问题。接下来我们看一下代码里面怎么使用。
import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class ExchangerTest {
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final Exchanger exchanger = new Exchanger(); service.execute(new Runnable() { public void run() { try {
String data1 = "zxx"; System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去"); Thread.sleep((long) (Math.random() * 10000)); String data2 = (String) exchanger.exchange(data1); System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2); } catch (Exception e) {
} } }); service.execute(new Runnable() { public void run() { try {
String data1 = "lhm"; System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去"); Thread.sleep((long) (Math.random() * 10000)); String data2 = (String) exchanger.exchange(data1); System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2); } catch (Exception e) {
} } }); } } |
只有当两个线程都运行到了交换数据的那行代码的时候,才可以交换数据,并继续向下运行。如果有一个线程先运行到了交换数据的那行代码的那个位置,这个线程无法继续向下运行,它必须等待另一个线程也运行到了交换数据的那行代码,然后两个线程交换数据,交换数据完了之后,才可以继续向下运行。