一、CyclicBarrier
这是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)(通过调用await()方法)。举个例子来讲,好比说一帮人组织去海边野餐, 他们会选择一个集合点(定为集合地点1),等人到齐了就出发,等到野餐完大家三三两两的分散去玩啦,最后大家又要约定一个集合地点(定为集合地点2),等人到齐了就返回。下面用一段代码来模拟这样一个场景:
/**
*CyclicBarrier通过调用自己的await()来达到控制线程运行状态的目的
*await()就是所谓的公共屏障点,在该屏障点被越过之后,可以通过再次调用来达到重用的目的
*通过构造函数CyclicBarrier(int num)来创建具有num个参与者的CyclicBarrier对象
*只有等到这num个参与者都执行到await()方法的调用位置,即到达了公共屏障点,才可以共同越过屏障,继续执行
*其中有一个方法getNumberWaiting(),用来返回在当前屏障点等待的参与者数目
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
//创建一个可根据需要创建新线程的线程池
ExecutorService service = Executors.newCachedThreadPool();
//创建一个有3个参与者的CyclicBarrier对象
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()*5000));//5秒内人都出发赶往集合地点1
System.out.println(Thread.currentThread().getName() + "已经到达集合地点1,"
+ (cb.getNumberWaiting()==2?"OK!人都到齐了,出发!": "正在等候..."));
cb.await();//在3个参与者都执行到了该方法之前,先到的线程会阻塞直到同时执行到该方法(即公共屏障点)
Thread.sleep((long)(Math.random()*5000));
System.out.println(Thread.currentThread().getName()+"饱餐一顿,四处转转去!");
cb.await();//等到用餐完毕(又一个公共屏障点)
Thread.sleep((long)(Math.random()*5000));//5秒内人都出发赶往集合地点2
System.out.println(Thread.currentThread().getName() + "已经到达集合地点2,"
+ (cb.getNumberWaiting()==2?"OK!人都到齐了,回家!": "正在等候..."));
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
}
}
二、Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个release(),添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore
只对可用许可的号码进行计数,并采取相应的行动。通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
举个例子来说,比如一个银行有三个窗口提供服务,有十个人在排队等待办理业务。每次最多只能有3个人同时办理业务,如果有任何一个窗口服务完毕,接下来排队的人之中就有一个去该窗口办理业务知道十个人都办理完毕。下面用一段代码来模拟这样一个场景:
/**
*Semaphore通过传入构造函数的许可数来完成对并发执行的线程数量的限制
*多个线程共享一个Semaphore对象
*该对象通过在每个线程中调用其acquire()方法来控制线程运行状态
*每执行一次acquire()许可数减1,release()则加1
*当许可数归零时其余线程只能在acquire()处随着它的阻塞而等待
*Semaphore类有一个availablePermits()用来获取剩余许可书
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//创建一个可根据需要创建新线程的线程池
ExecutorService service = Executors.newCachedThreadPool();
//创建一个具有给定许可数的Semaphore对象
final Semaphore sp = new Semaphore(3);
//创建10等待办理业务的客户线程
for(int i=0;i<10;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
sp.acquire();//获取许可或者等待获取许可,许可数减1
System.out.println("客户" + Thread.currentThread().getName() +
"开始办理业务,当前有" + (3-sp.availablePermits()) + "个客户正在办理业务");
Thread.sleep((long)(Math.random()*5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("客户" + Thread.currentThread().getName() + "业务办理完毕");
sp.release();//释放权限,许可数加1
//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("客户" + Thread.currentThread().getName() +
"已离开,当前有" + (3-sp.availablePermits()) + "个客户正在办理业务");
}
};
service.execute(runnable);
}
service.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
}
}
三、CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 用给定的计数初始化 CountDownLatch,然后每个线程调用
调countDown()方法会导致计数减一,在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await
的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。
一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch
用作一个简单的开/关锁存器,或入口:在通过调用countDown()
的线程打开入口前,所有调用await
的线程都一直在入口处等待。用N 初始化的CountDownLatch
可以使一个线程在N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。它不要求调用countDown
方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await
。
举个例子来说,在一场运动会中有一个长跑比赛,有八名运动员,只用等全部运动员都跑完赛程才裁判才可以统计成绩,下面也用一段代码来实现这个场景:
/***
*CountDownLatch类其实就是通过调用自己的await()方法来达到其控制线程状态的目的
*CountDownLatch通过调用自己的countDown()来实现其所持计数减1的操作,此方法不会阻塞线程
*计数不归零,await()阻塞,则其所在线程即阻塞
*计数归零,await()解除阻塞,则其所在线程也解除阻塞
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();//创建一个可根据需要创建新线程的线程池
final CountDownLatch cdOrder = new CountDownLatch(1);//创建一个所持计数器为1的CountDownLatch对象
final CountDownLatch cdAnswer = new CountDownLatch(3);//创建一个所持计数器为1的CountDownLatch对象
//创建8个运动员线程
for(int i=0;i<8;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
System.out.println("运动员" + Thread.currentThread().getName() + "正等待发令枪.....");
cdOrder.await();//阻塞运动员线程,等待发令枪即(cdOrder所持计数器归零)
System.out.println("运动员" + Thread.currentThread().getName() + "起跑!");
Thread.sleep((long)(Math.random()*5000));
System.out.println("运动员" + Thread.currentThread().getName() + "已到达终点!");
cdAnswer.countDown();//cdAnswer所持计数减1,已记录未到达终点运动员线程数
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long)(Math.random()*5000));
System.out.println("教练" + Thread.currentThread().getName() + "即将发布命令");
Thread.sleep((long)(Math.random()*5000));
System.out.println("啪.............!!!!!!!1");
cdOrder.countDown();//教练打响发令强,即cdOrder所持计数减1归零,运动员线程解除阻塞状态
cdAnswer.await();//阻塞(教练)主线程,直到所有运动员到达终点,即cdAnswer所持计数器归零以
System.out.println("教练" + Thread.currentThread().getName() + "开始统计成绩!");
} catch (Exception e) {
e.printStackTrace();
}
service.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
}
}
四、Exchanger
可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给exchange
方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为SynchronousQueue
的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。还有一个应用,就是如果另一个线程已经在交换点等待,则出于线程调度目的,继续执行此线程,并接收当前线程传入的对象。当前线程立即返回,接收其他线程传递的交换对象
这个可以用一个我取邮件的例子来演示一下,具体代码如下:
/**
*Exchanger用于线程对之间的数据交换,所以一对线程要共享一个Exchanger对象
*Exchanger对象通过调用exchange(arg)方法来实现数据交换
*两条线程必须都执行到了exchange(arg)方法调用处才能完成数据交换
*否则先执行到的线程阻塞等待(严格按照一手交钱一手交货的模式走)
*/
public class ExchangerDemo {
public static void main(String[] args) {
//创建一个可根据需要创建新线程的线程池
ExecutorService service = Executors.newCachedThreadPool();
//创建一个由线程对共享的Exchanger对象
final Exchanger<String> exchanger = new Exchanger<String>();
//提交并执行邮递员线程
service.execute(new Runnable(){
public void run() {
try {
String data1 = "邮件";
System.out.println("邮递员" + Thread.currentThread().getName() + "正等待jzk取" + data1);
Thread.sleep(3000);//此处模拟邮递员先到,等待我
//取到交换的数据并返回,若对方未还调用执行到exchang()方法,则等待
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 = "22元";
System.out.println("我" + Thread.currentThread().getName() +
"正拿着" + data1 +"邮费去取邮件");
Thread.sleep(6000);//此处模拟我后到,完成一手交钱一手交货
//取到交换的数据并返回,若对方未还调用执行到exchang()方法,则等待
String data2 = (String)exchanger.exchange(data1);
System.out.println("我" + Thread.currentThread().getName() + "取到了" + data2);
}catch(Exception e){
e.printStackTrace();
}
}
});
service.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
}
}