该篇文章介绍CountDownLatch,CycliBarrier,Semaphore,Exchanger并发工具类的使用
CountDownLatch
CountDownLatch允许一个或者多个线程等待其他线程完成操作。
在多线程中可能出现A线程需要等待B线程和C线程执行完毕才会执行,而A线程又不知道B和C线程具体什么时候能够结束,所以需要一种通知方式告诉A线程B和C线程已经执行完毕,在Java中可以通过先使A线程await(),等待B和C线程执行完毕在notifyAll()唤醒A线程。
Threadl类有一个join()方法可以实现这个功能,
join():在一个线程中调用另一个线程的join(),会使得被调用join()的线程执行完毕,调用线程才会继续执行下去。
查看Thread类的join()方法
直到join线程中止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的
使用例子:main线程需等待thread1和thread2线程执行完毕才会继续执行
结果:
CountDownLatch的使用
CountDownLatch有着和join()相同的功能,但是比join()更加灵活,功能也更加强大。CountDownLatch可以在不同线程不同位置使用。
初始化
CountDownLatch countDownLatch = new CountDownLatch(int count);
count:表示一个计数值,每调用一次countDown()方法,该计数值会减一,当count=0时,其他线程执行完毕,唤醒主线程。
使用CountDownLatch 完成上面的例子
注意:使用CountDownLatch的时候确保计数值在最后为零,要不然主线程会一直等待下去,所以调用countDown()方法的时候,确保线程会执行到该方法。也可以设置超时时间await(long timeout, TimeUnit unit),
结果:
CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
在不同线程之间设置屏障
先到达的线程进入等待,等待屏障打开
当所有线程到达屏障后,屏障打开
初始化
第一种
CyclicBarrier c = new CyclicBarrier(int parties);
parties:表示拦截线程的数量
每个线程调用CyclicBarrier 的await(),表示当前线程到达屏障,当前线程会进入阻塞。当所有规定数量的线程到达屏障后,之前阻塞的线程线程都会唤醒。继续执行。(屏障打开后,所有线程的执行顺序是不确定的,根据线程竞争cpu,获取执行时间)
第二种
CyclicBarrier c = new CyclicBarrier(int parties, Runnable barrierAction)
barrierAction:屏障线程
设置屏障线程,既然所有线程都在同一个点阻塞(同步点),那么这个阻塞点就很有意义,所以就有了屏障线程的出现,在所有线程到达屏障,但是屏障开没有打开,这个时间就是屏障线程的工作时间。
例如有一个5000大小的数组,统计数组中数字的和。可以将这个数组分成5分,每个线程处理1000个数字之和,当5个线程执行完毕,再有一个屏障线程处理这五个线程的和,这样得到最终的结果。
对上面的例子进行编码
结果:
CyclicBarrier常用API
CyclicBarrier(parties)
初始化相互等待的线程数量的构造方法。
CyclicBarrier(parties,Runnable barrierAction)
初始化相互等待的线程数量以及屏障线程的构造方法。屏障线程的运行时机:等待的线程数量=parties之后,CyclicBarrier打开屏障之前。
举例:在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。
getParties()
获取CyclicBarrier打开屏障的线程数量,也称为方数。
getNumberWaiting()
获取正在CyclicBarrier上等待的线程数量。
await()
在CyclicBarrier上进行阻塞等待,直到发生以下情形之一:
在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
await(timeout,TimeUnit)
在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:
在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
当前线程等待超时,则抛出TimeoutException异常,并停止等待,继续执行。
其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
isBroken()
获取是否破损标志位broken的值,此值有以下几种情况:
CyclicBarrier初始化时,broken=false,表示屏障未破损。
如果正在等待的线程被中断,则broken=true,表示屏障破损。
如果正在等待的线程超时,则broken=true,表示屏障破损。
如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。
reset()
使得CyclicBarrier回归初始状态,直观来看它做了两件事:
如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。将是否破损标志位broken置为false。
测试reset()
结果
CountDownLatch和CyclicBarrier的区别
CountDownLatch和CyclicBarrier都有让多个线程等待同步然后再开始下一步动作的意思,但是CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点。
semaPhore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
比如××马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入××马路,但是如果前一百辆中有5辆车已经离开了××马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
例子模拟100个线程 ,semaPhore限制10个线程。
结果:每隔2秒打印10个线程
semaPhore常用API
.tryAcquire():尝试获取许可证,立即获取返回true,否则返回false。
·intavailablePermits():返回此信号量中当前可用的许可证数。
·intgetQueueLength():返回正在等待获取许可证的线程数。
·booleanhasQueuedThreads():是否有线程正在等待获取许可证。
·void reducePermits(int reduction):减少reduction个许可证,是个protected方法。
·Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法
Exchanger
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
例子:模拟A,B两个线程之间信息的交换
结果:
如果两个线程有一个没有执行exchange()方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)设置最大等待时长。