前言
线程之前通常需要进一步的协调工作,来完成相应比较复杂的并发任务,使用wait/notify等方法都是底层实现。现在我们需要的更为抽象满足具体实际业务的一些方法,这时候我们就会用到相应的5种同步辅助类。
主要参考资料:这里写链接内容,
- CountDownLatch:它允许一个或多个线程一直等待,直到其他线程执行完后再执行。利用它可以实现类似于计数器的功能。
- Semaphore:信号量,如果学过操作系统中PV操作的话,对这个应该很熟悉。是表示当前可用的资源数量。
- CyclicBarrier:回环栅栏(翻译:可循环使用的屏障),作用是将一组线程到达一种状态(或者屏障,也可以叫同步点)时等待阻塞,直到最后一个到达屏障(同步点)时,屏障才会打开。所有的线程才能进行执行。
- Phaser:一张可重用的同步屏障,作用是在线程的阶段都对线程进行同步,等所有的线程完成这一步后才能继续进行下一步。
- Exchanger:提供一个同步点,在这个同步点。一对线程相互比较彼此的数据。
一、CountDownLatch
//唯一的构造方法
public CountDownLatch(int count) { }; //参数count为计数值
//CountDownLatch的三个方法
public void await() throws InterruptedException { }; //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减1
比如:有主线程任务A,它需要等待其他两个线程任务B与C执行完毕后才能执行
则线程任务B与C分别调用
CountDownLatch
的countDown
方法时,count
就会减一,直至减为零。主线程任务A使用
await
方法等待,当count
的值变为零,执行await
的主线程A继续执行。
实现代码:
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(2);//计数数count=2
//线程任务B
new Thread(){
public void run() {
try {
System.out.println("子线程任务B:"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程任务B:"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();//count--
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
//线程任务C
new Thread(){
public void run() {
try {
System.out.println("子线程任务C: "+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程任务C:"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();//count--
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
//主线程任务A
new Thread(){
public void run() {
try {
latch.await();//等待两个任务BC完毕
System.out.println("主线程任务A:"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("主线程任务A:"+Thread.currentThread().getName()+"执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
当我们不使用await()
方法时:主线程A与线程B、C之间执行顺序是不固定的,不会等待BC完毕后才执行
使用await()
方法之后,线程A总会等待B、C执行完毕之后才执行。
与join()
方法的区别:
join()
方法是,不停检查thread
是否存活,如果存活则让当前线程永远wait
,直到thread
线程终止,线程的this.notifyAll
就会被调用。CountDownLatch
是一直检测计数器count
是否为0,不为0则一直阻塞,当计数器的值为0时,因调用await()
方法被阻塞的线程会被唤醒,继续执行。
二、CyclicBarrier
CyclicBarrier
有两个构造器:
//parties: 表示让多少个线程等待到barrier/同步点;
//barrierAction: 表示线程到达barrier状态之后要执行的动作。
public CyclicBarrier(int parties, Runnable barrierAction) {}
public CyclicBarrier(int parties) {}
注意:barrierAction
是优先于await()
后面的方法的,即线程达到同步点之后,先执行barrierAction
后执行await()
后面的方法。
CycilcBarrier
有两个await()
方法:跟CountDownLatch
的的两个await()
类似,但主要不是等待count
为0,而是等待线程是否到达barrier
状态。
public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException {
比如:现在有四个线程,每个线程都分为两个阶段,一个执行写操作,一个是执行读操作。当前我们需要4个线程都执行完毕写操作(达到写状态)之后,随机选一个线程再进行读操作。
public class CyclicBarrierDemo {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N,new Runnable() {
@Override
public void run() {
System.out.println("所有写操作执行完毕,下面继续执行读操作...");
System.out.println("线程"+Thread.currentThread().getName()+"正在读数据...");
}
});
System.out.println("所有线程开始执行写操作...");
for(int i=0;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
Thread.sleep(3000);
System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
cyclicBarrier.await();//所有线程等待达到barrier屏障(写操作完毕)之后被阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
}
}
}
执行结果如下:当0-3线程中任意一个线程到达写状态之后,开始等待其他的线程完成写操作。当线程0-3全部执行完写操作后,随机选取一个线程就开始执行读操作。
CyclicBarrier
的重用:CyclicBarrier
到达屏障点之后还可以进行再一次的重复使用,而CountDownLatch
是不能的。
System.out.println("所有线程开始执行写操作...");
for(int i=0;i<N;i++)
new Writer(barrier).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CyclicBarrier重用...");
for(int i=0;i<N;i++) {
new Writer(barrier).start();
}
0-3线程达到barrier
之后,CyclicBarrier
重用创建3-7线程执行相同的任务:
三、Semaphore
Semaphore
表示线程公共申请的资源个数,可以控制同时访问的线程个数,通过 acquire()
获取一个许可,如果没有就等待,而 release()
释放一个许可。
Semaphore
构造方法:
//参数permits表示许可数目,即同时可以允许多少线程进行访问
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair) {
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore
的几个重要的方法:acquire()、release()、tryAcquire()、availablePermits()、availablePermits()
方法:
(1)acquire()
与release()
方法:获得、释放许可。相当于PV
操作中的P(S)、V(S)
操作
public void acquire() throws InterruptedException { } //获取一个许可
public void acquire(int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release(int permits) { } //释放permits个许可
(2)tryAcquire()
方法:尝试获得许可,在release()方法释放之前,必须获得许可:
public boolean tryAcquire() { }; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
(3)availablePermits()
方法得到可用的许可数目:
public int availablePermits() {
return sync.getPermits();
}
比如:我们有10个人,但是只有5台打印机,一台打印机只能被一个人占用。只有打印机使用结束后,下一个人才会占用该打印机。此时的资源为5台打印机,也就Semaphore
是允许最多的并发线程数目permits
为5。
public class SemaphoreDemo {
private static final int THREAD_COUNT = 10; //10个线程
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); //获得线程池
private static Semaphore semaphore = new Semaphore(5); //只允许最高5个线程并发
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
print(i);
}
threadPool.shutdown();
}
public static void print(int num) {
threadPool.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();//默认5个线程一起运行,即每次获取5个permits
System.out.println("员工"+num+"占用一台打印机正在打印...");
Thread.sleep(3000);
System.out.println("员工"+num+"释放出打印机");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
四、Exchanger
提供一个同步点,在这同步点两个线程进行交换数据,如果其中一个先执行exchange()
方法,它就会一直等待第二个线程也执行exchange()
方法。两个同时到达同步点之后就可以交换数据,但需要注意的是该线程只能是两个线程之间的交换数据,并不支持更多的线程。
比如:我们可以用来对比数据,线程A传来数据1,线程B传来数据2,我们需要比较数据1跟数据2。如果相等则可以认为是有效数据,进行入库操作;不相等则是无效数据。
public class ExchangerDemo {
private static final Exchanger<String> exchanger = new Exchanger<String>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
//线程A
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String A = "data1";// 线程A传入数据1
String B=exchanger.exchange(A); //如果线程A先执行exchange方法,则线程A等待阻塞。直到线程B也执行exchange方法
System.out.println("我是A线程,这是我的视角:我传入的数据是"+A+ ", 而B线程传入的是"+B);
} catch (InterruptedException e) {
}
}
});
//线程B
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String B = "data2";// 线程B传入数据2
String A = exchanger.exchange(B);
System.out.println("我是B线程,这是我的视角:我传入的数据是"+B+", 而A线程传入的是"+A);
} catch (InterruptedException e) {
}
}
});
threadPool.shutdown();
}
}
线程AB所传入的数据是不可见的,但是在执行exchange()
方法之后,线程AB进行了沟通,将彼此的数据进行了交换。
五、Phaser
- Phaser(int parties),构造方法,与CountDownLatch一样,传入同步的线程数。
- register()动态添加一个或多个参与者
- getPhase()用于获取当前的阶段.默认阶段从0开始.
- arriveAndDeregister()方法,动态撤销线程在phaser的注册,通知phaser对象,该线程已经结束该阶段且不参与后面阶段。
- arriveAndAwaitAdvance()方法,类似await()方法,记录到达线程数,阻塞等待其他线程到达同步点后再继续执行。
- onAdvance(int phase,int registeredParties):这个方法比较特殊,表示当进入下一个phase时可以进行的事件处理,如果返回true表示此Phaser应该终止(此后将会把Phaser的状态为termination,即isTermination()将返回true。),否则可以继续进行。类似于相当于CyclicBarrier的barrierAction方法。phase参数表示当前周期数,registeredParties表示当前已经注册的parties个数。
public class PhaserDemo {
private static final int THREAD_COUNT = 5; //5个线程
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); //获得线程池
private static final int parties = 3;//三个阶段
public static void main(String[] args) {
final Phaser phaser = new Phaser(parties);
for (int i = 0; i < THREAD_COUNT; i++) {
phaser.register(); //每个线程注册一个phaser
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
while (!phaser.isTerminated()) {
//第一阶段
System.out.println(Thread.currentThread().getName()+"到达第一阶段:准备阶段");
phaser.arriveAndAwaitAdvance();//所有线程到达第一阶段阻塞
//第二阶段
System.out.println(Thread.currentThread().getName()+"到达第二阶段:计算阶段");
phaser.arriveAndAwaitAdvance();
//第三阶段
System.out.println(Thread.currentThread().getName()+"到达第三阶段:归档阶段");
phaser.arriveAndDeregister(); //所有阶段完毕后线程注销
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}