学习Java5.0 并发编程包--线程工具类
并发包Java.util.concurrent不仅提供了线程池相关的接口和类,同时也提供了几个很有用的工具类!这里打算介绍4个,包括Semaphore,CyclicBarrier,CountDownLatch,Exchanger。在实际编码中,进行多线程的并发互斥和同步控制,使用这些工具类可以节省不少代码!
【Semaphore】
信号灯工具类。我们想想以前控制线程互斥的方式:synchronized & Lock,都达到了什么效果? synchronized关键字,保证了一段多线程敏感代码一次只会有一个线程运行,Lock中“写锁”也会是这个效果,多个持有“读锁”的线程就可以多线程并发访问资源。但我们现在有这个需求,控制某个资源最多有几个线程访问。如果自己实现这个需求,还是需要一定的代码量的,利用Semaphore就可以轻松实现这个需求,我们看这个例子:
- package cn.test;
- import java.util.concurrent.Semaphore;
- public class SemaphoreTest {
- public static void main(String[] args) {
- // 创建一个资源,最大允许2个线程同时并发
- final Resources resource = new Resources(2);
- // 起5个线程,开始处理资源!
- for(int i=0; i<5; i++){
- new Thread(new Runnable(){
- @Override
- public void run() {
- resource.processResource();
- }}, "Thread " + (i+1)).start();
- }
- }
- /**
- * 资源类,该资源允许多线程并发访问!但为了保证可用性,外界可以设定最多几个线程进行并发!
- */
- static class Resources {
- // 信号灯,用于控制该资源的并发访问数量
- Semaphore semaphore = null;
- public Resources(int concurrentNum){
- if(concurrentNum < 1){
- throw new IllegalArgumentException("该资源的并发数量应该>0!");
- }
- semaphore = new Semaphore(concurrentNum);
- }
- public void processResource() {
- System.out.println(Thread.currentThread().getName() + " 正试图获取准许进入资源.....");
- try {
- // 获取一个信号灯,如果没有了,则线程阻塞在这里
- semaphore.acquire();
- System.out.println(Thread.currentThread().getName() + " 已获取操作资源许可,准备操作!");
- // 进行资源操作,这里让线程sleep 代表耗时任务
- Thread.sleep(2000);
- System.out.println(Thread.currentThread().getName() + " 执行完毕,准备释放信号灯!");
- semaphore.release();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
输出为:
- Thread 1 正试图获取准许进入资源.....
- Thread 3 正试图获取准许进入资源.....
- Thread 4 正试图获取准许进入资源.....
- Thread 2 正试图获取准许进入资源.....
- Thread 3 已获取操作资源许可,准备操作!
- Thread 1 已获取操作资源许可,准备操作!
- Thread 5 正试图获取准许进入资源.....
- Thread 3 执行完毕,准备释放信号灯!
- Thread 1 执行完毕,准备释放信号灯!
- Thread 2 已获取操作资源许可,准备操作!
- Thread 4 已获取操作资源许可,准备操作!
- Thread 4 执行完毕,准备释放信号灯!
- Thread 2 执行完毕,准备释放信号灯!
- Thread 5 已获取操作资源许可,准备操作!
- Thread 5 执行完毕,准备释放信号灯!
我们这个资源最多允许两个线程进入,我们看,开始是4个线程同时申请进入,最后只有两个线程申请成功!只有执行完毕,并且释放信号灯,其他线程才可以成功申请进入!创建Semaphore时,需要设定信号灯的数量,这个数量最终用于控制线程的数量!调用Semaphore对象的acquire()方法来申请信号灯,如果还有,则申请成功,否则线程就会阻塞在这里!线程执行完毕后,要调用Semaphore对象的release()方法来释放信号灯!通过Semaphore我们可以很轻松地对某个资源的操作线程数进行控制!
Semaphore semaphore = new Semaphore(1)。这个Semaphore对象的作用和synchronized的功效一样!但他有个优点:即某个线程可以调用semaphore的release方法来释放其他线程把持的信号灯!对于synchronized修饰的代码段,如果一个线程进入后,因某种情况如死锁导致无法正常从方法块中出来时,其他线程只能阻塞等待,必要时可能需要人工重启解决。如果使用Semaphore,可以设定一个线程进行监控,如果某个其他线程长时间把持信号灯不放,就可以认为其出现异常情况,可以主动干预释放其把持的信号灯!这种特性的使用,需要根据具体情况仔细才可使用!
【CyclicBarrier】
“可循环的拦阻器”。我们直接用一个例子来解释其用法。假设,一个任务由两个子任务组成,每个子任务包含两部分,子任务2必须要等子任务1完成后才能开始!我们有两个线程,安排线程1执行子任务1第1部分,子任务2第1部分,线程2执行子任务1第2部分,子任务2第2部分,两个线程共同将子任务1完成后才能开始执行子任务2,我们可以通过CyclicBarrier来实现:
- package cn.test;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- public class CyclicBarrierTest {
- public static void main(String[] args) {
- /**
- * 创建任务,该任务由两个子任务组成,任务1和任务2.每个子任务包含两部分,任务2要依赖于任务1。
- * 即任务2开始执行时,任务1必须完成。我们创建两个线程来做这个。任务内部通过CyclicBarrier来
- * 控制子任务的关系!
- */
- final MyTask task = new MyTask();
- new Thread(new Runnable(){
- @Override
- public void run() {
- task.task1Part1();
- task.task2Part1();
- }}, "线程一").start();
- new Thread(new Runnable(){
- @Override
- public void run() {
- task.task1Part2();
- task.task2Part2();
- }}, "线程二").start();
- }
- static class MyTask {
- CyclicBarrier barrier = new CyclicBarrier(2);
- public void task1Part1() {
- System.out.println(Thread.currentThread().getName() + " 开始执行任务1的部分1!");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 将任务1的第1部分执行完毕!!");
- }
- public void task1Part2() {
- System.out.println(Thread.currentThread().getName() + " 开始执行任务1的部分2!");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 将任务1的第2部分执行完毕!!");
- }
- public void task2Part1() {
- try {
- barrier.await();
- System.out.println(Thread.currentThread().getName() + " 开始执行任务2的部分1!");
- Thread.sleep(1000);
- System.out.println(Thread.currentThread().getName() + " 将任务2的第1部分执行完毕!!");
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- public void task2Part2() {
- try {
- barrier.await();
- System.out.println(Thread.currentThread().getName() + " 开始执行任务2的部分2!");
- Thread.sleep(2000);
- System.out.println(Thread.currentThread().getName() + " 将任务2的第2部分执行完毕!!");
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- }
- }
输出结果为:
- 线程一 开始执行任务1的部分1!
- 线程二 开始执行任务1的部分2!
- 线程一 将任务1的第1部分执行完毕!!
- 线程二 将任务1的第2部分执行完毕!!
- 线程二 开始执行任务2的部分2!
- 线程一 开始执行任务2的部分1!
- 线程一 将任务2的第1部分执行完毕!!
- 线程二 将任务2的第2部分执行完毕!!
创建CyclicBarrier对象,需要一个整形参数,表示这个对象一次阻拦多少个线程。当阻拦的线程数一到,才准许放行!调用CyclicBarrier对象的await()方法,如果此时线程数没到,则线程阻塞在这里,否则,所有阻塞的线程均放行!CyclicBarrier可以反复使用,放行一批后,另一批过来,又是重复这个过程!!
CountDownLatch 和 Exchanger,我们下篇再讲!

浙公网安备 33010602011771号