并发包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对象的作用和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,我们下篇再讲!