闲话不说,先来看一段代码:
{ IValueCallback remoteCallback = new IValueCallback.Stub() { <strong><span style="color:#ff0000;">(B)</span></strong> public void onReceiveValue(final Bundle value) throws RemoteException { synchronized (syncObject) { mReturnValue = arg.result; syncObject.notify(); //执行完毕,notify通知代码继续进行 } } }; boolean bSuccess = false; synchronized (syncObject) { <strong><span style="color:#ff0000;">(A) </span></strong>sendRequest(CommandConstant.COMMAND_NAVIGATION_ITEM_EXIST, arg, remoteCallback); try { syncObject.wait(5000); //等待Callback部分执行完毕 } catch (InterruptedException e) { e.printStackTrace(); } } return mReturnValue; }
来分析一下这段代码的作用: 执行(A)块后,让线程等待——> (B)块返回结果,并通知结束等待——>返回结果值。
抛开可读性不谈,这样的处理是可以完成【同步请求】,But, 来看一下可能有的问题—— 如果IValueCallBack里有两个回调函数,而返回结果取决于两个回调返回的值,那么同步锁要怎么加?怎么notify?
思考了一分钟,还是有些头疼?没关系,下面两位主角CountDownLatch 和
CyclicBarrier 出场了。(从未听说过Java这两个类的请举手!我先举为敬……)
先介绍一下CountDownLatch 这个类:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
A
is initialized with a given count. The
CountDownLatch
await
methods block until the current count reaches zero due to invocations of the
countDown()
method, after which all waiting threads are released and any subsequent invocations of
await
return immediately. This is a one-shot phenomenon -- the count cannot be reset.
也就是说:可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值 (countDown()
方法)。
你可以向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。
上面的代码修改为:
{ CountDownLatch cdl = new CountDownLatch (2);//2次的计数器 IValueCallback remoteCallback = new IValueCallback.Stub() { (B) public void onReceiveValueA(final Bundle value) throws RemoteException { mReturnValue = arg.result && mReturnValue ; cdl.countDown(); // 减少一次计数 } public void onReceiveValueB(final Bundle value) throws RemoteException { mReturnValue = arg.result&& mReturnValue ; cdl.countDown(); // 减少一次计数 } } }; boolean bSuccess = false; (A) sendRequest(CommandConstant.COMMAND_NAVIGATION_ITEM_EXIST, arg, remoteCallback); try { cdl.await() //等计数器清零后返回结果 } catch (InterruptedException e) { e.printStackTrace(); } return mReturnValue; }
可以看到:回调函数增加成了两个,但CountDownLatch 类轻易的解决了这个问题,而且避免了显式的使用同步锁。
而这个类真正强大的地方在于,它可以灵活的用于参数传递,去编写更多可以解决同步问题的代码,并带来一种比显式同步锁更加清晰的思路。
懂了这个类,CyclicBarrier 也就容易懂了,CyclicBarrier 类用法类似,但它用于增加到一个固定值时触发一段操作。
下面用LOL举例,进入英雄联盟这个游戏,需要 A)读进度条 2)10个人都进入后才能开始游戏 3)所有人购买装备 ,那么处理同步的方法,可以简单的用这个类写一个Demo。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class LOLGame { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() { @Override public void run() { System.out.println("欢迎来到召唤师峡谷!"); } }); for (int i = 0; i < 10; i++) { new Thread(new Player(i, cyclicBarrier)).start(); } } } class Player implements Runnable { private CyclicBarrier cyclicBarrier; private int id; public Player(int id, CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; this.id = id; } @Override public void run() { try { System.out.println("玩家" + id + "正在读进度条..."); cyclicBarrier.await(); System.out.println("玩家" + id + "购买装备..."); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
至于CountDownLatch
的Demo就不再写了,大家可以举一反三,上网搜一下资料。有很多问题的解都很精妙。
官方文档中也有一个比较精妙的例子
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html
最后的总结:
1)这两个类是在Java5.0引入的。
2) CountDownLatch 用于逆向计数,CyclicBarrier 用于正向计数,两者都是在计数完成后通知await()部分继续执行。
3) 粗测之后发现,这两个工具的性能应该是比显式使用同步锁要更高,但我分享这个不只是出于改造代码的目的。
抛开性能不谈,我个人认为,这两个类最大的意义在于:它们提供了一种用原子计数器解决并发线程问题的思路,将多个线程的同步问题变得非常清晰可抽象。与晦涩的同步锁相比,计数器的实现会更容易将多线程问题抽象,将精力投入到具体逻辑的严谨性,而非投入精力为可能的死锁和性能消耗而头疼。