前言
Semaphore、CountDownLatch、CyclicBarrier、Exchanger、Phaser
信号量、倒计时门栓、循环屏障、交换器、移相器
一、什么是同步器?
线程之间相互合作时,需要用到同步器来完成同步,java.util.concurrent包提供了几个能帮助我们管理相互合作的线程集的类。这些机制具有为线程之间的公用集结点提供“预置功能”,这句话通俗理解就是线程集要等待一个起始点一起开始运行。
二、同步器的使用
1.信号量
概念:一个信号量管理许多许可证。为了通过信号量,线程通过调用acquire请求许可。实际上没有真实的许可对象,信号量仅维护一个计数。许可的数目是固定的,由此控制了同一时刻通过的线程数量。其他线程可以通过调用release释放许可,而且许可不是必须由获取它的线程来释放,任意线程可以释放任意数量的许可,这会导致许可数目超出起始数量。信号量还支持是否公平申请。
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 15; i++) {
new Thread(new SemaphoreTask(semaphore)).start();
}
}
static class SemaphoreTask implements Runnable {
private Semaphore semaphore;
public SemaphoreTask(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
// 获取许可该方法会阻塞直到获取到许可
this.semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " start task...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " task completed");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 一般都是在finally中自己释放获取的许可
this.semaphore.release();
}
}
}
}
2.倒计时门栓
一个倒计时门栓让一个线程集等待直到计数变为0才开始工作,任务线程每完成一个就减1,倒计时门栓是一次性的一旦计数为0,就不能再重用。
场景1、多线程等待主线程任务:可以用于秒杀场景
场景2、多线程任务主线程等待:一般是用于初始化工作
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
test1();
test2();
}
/**
* 多线程等待主线程任务:可以用于秒杀场景
* @throws InterruptedException
*/
static void test2() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(new CountDownLatchTask2(countDownLatch)).start();
}
// 主线程睡眠模拟执行任务
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
// 完成任务
countDownLatch.countDown();
}
static class CountDownLatchTask2 implements Runnable {
CountDownLatch countDownLatch;
public CountDownLatchTask2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 就绪
System.out.println(Thread.currentThread().getName() + " ready....");
try {
// 等待开门
this.countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
onlyOnde();
}
static boolean flag;
static synchronized void onlyOnde() {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " doing....");
flag = true;
}
}
}
/**
* 多线程任务主线程等待:一般是用于初始化工作
* @throws InterruptedException
*/
static void test1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " start init task on child thread...");
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(new CountDownLatchTask(countDownLatch)).start();
}
// 等待初始化完成
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " server started");
}
static class CountDownLatchTask implements Runnable {
CountDownLatch countDownLatch;
public CountDownLatchTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 任务线程睡眠模拟执行任务
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " task completed");
// 完成任务
this.countDownLatch.countDown();
}
}
}
3.循环屏障
CyclicBarrier类实现了一个集结点称为屏障,一旦所有线程到达这个屏障则撤销屏障线程继续运行。Cyclic(循环)意思是可以设置多个屏障并且可以使用reset方法重置来达到循环使用的目的,多个线程需要多次同步数据的时候使用,否则只有一个屏障使用CountDownLatch就可以了。循环屏障还可以设置所有线程达到屏障后执行一个任务,该任务有最后一个到达的线程执行。
最直观的一个列子就是英雄组队游戏时场景 等待加入->选择英雄->初始化加载
public class CyclicBarrierTest {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(gamerCount, new Runnable() {
@Override
public void run() {
// 所有玩家每次都到达一个屏障时调用(由最后到达的线程调用)
switch ((int) countDownLatch.getCount()) {
case 3: System.out.println(Thread.currentThread().getName() + " join ready..."); break;
case 2: System.out.println(Thread.currentThread().getName() + " choose ready..."); break;
case 1: System.out.println(Thread.currentThread().getName() + " init ready..."); break;
}
// 屏障倒计
countDownLatch.countDown();
}
});
test1(cyclicBarrier);
}
// 倒计时门栓,用于倒计玩家到达的屏障点
static CountDownLatch countDownLatch;
static final int gamerCount = 5;
static void test1(CyclicBarrier cyclicBarrier) throws InterruptedException {
System.out.println("============================================================");
System.out.println(Thread.currentThread().getName() + " star join game...");
countDownLatch = new CountDownLatch(3);
for (int i = 0; i < gamerCount; i++) {
new Thread(new CyclicBarrierTask(cyclicBarrier)).start();
}
// 屏障倒计等待:等待任务完成,这里模拟屏障的循环使用
countDownLatch.await();
cyclicBarrier.reset();
test1(cyclicBarrier);
}
/**
* 英雄组队游戏案例
*/
static class CyclicBarrierTask implements Runnable {
CyclicBarrier cyclicBarrier;
public CyclicBarrierTask(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
// 等待组队
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " wating join...");
cyclicBarrier.await();
// 选择英雄
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " choose hero...");
cyclicBarrier.await();
// 游戏初始化加载
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " init data...");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " start game...");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
4.交换器
当两个线程在同一个数据缓冲器工作的时时,就可以使用交换器,当两个线程需要同步数据时使用,一般用于数据校验。
public class ExchangerTest {
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(){
@Override
public void run() {
try {
String A = "Apple";
System.out.println(Thread.currentThread().getName() + " A 线程开始处理任务...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " A 线程任务处理完成等待交换...");
String B = exchanger.exchange(A);
System.out.println(Thread.currentThread().getName() + " A 交换过程: " + A + " --> " + B);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
@Override
public void run() {
try {
String B = "Orange";
System.out.println(Thread.currentThread().getName() + " B 线程开始处理任务...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " B 线程任务处理完成等待交换...");
String A = exchanger.exchange(B);
System.out.println(Thread.currentThread().getName() + " B 交换过程: " + B + " --> " + A);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
5.移相器
Phaser相对来说较复杂一些,一般可以看做是循环屏障和倒计时门栓的结合体,如上面3我写到的CyclicBarrierTest中就使用到了CountDownLatch来记录任务阶段。移相器可能不好理解称之为阶段性同步器更好理解。Pahser可以继承并重写onAdvance方法来执行阶段性工作,Pahser更特殊的是在运行时可以任意注册或注销任务线程,Phaser还提供了较多的方法共我们使用:register:注册、arriveAndAwaitAdvance:到达并等待、arriveAndDeregister:达到并注销、arrive:达到结束...
public class PhaserTest {
public static void main(String[] args) {
Phaser phaser = new MinePhaser(5);
for (int i = 0; i < 5; i++) {
new Thread(new PhaserTask1(phaser)).start();
}
// 动态注册一个线程并等待其他线程到达,只是用于阻塞等待(不然直接执行下面代码了)
phaser.register();
phaser.arriveAndAwaitAdvance();
// 动态注册3个整理任务线程(公共任务的整理任务线程不够)
phaser.register();
phaser.register();
for (int i = 0; i < 3; i++) {
new Thread(new PhaserTask2(phaser)).start();
}
// 动态注册一个线程并等待其他线程到达,只是用于阻塞等待(不然直接执行下面代码了)
phaser.register();
phaser.arriveAndAwaitAdvance();
// 需要注销上面动态注册的四个线程,不然永远达不到终点
phaser.arriveAndDeregister();
phaser.arriveAndDeregister();
phaser.arriveAndDeregister();
phaser.arriveAndDeregister();
}
/**
* 自定义移相器并重写onAdvance
*/
static class MinePhaser extends Phaser {
public MinePhaser(int parties) {
super(parties);
}
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("第一阶段,收集完毕!参与者数量:" + registeredParties);
break;
case 1:
System.out.println("第二阶段,整理完毕!参与者数量:" + registeredParties);
break;
case 2:
System.out.println("第三阶段,记录完毕!参与者数量:" + registeredParties);
break;
}
return super.onAdvance(phase, registeredParties);
}
}
/**
* 公共任务
*/
static class PhaserTask1 implements Runnable {
Phaser phaser;
public PhaserTask1(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " 已爬完");
this.phaser.arriveAndAwaitAdvance();
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " 已整理");
this.phaser.arriveAndAwaitAdvance();
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " 已记录");
this.phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 整理任务
*/
static class PhaserTask2 implements Runnable {
Phaser phaser;
public PhaserTask2(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(Thread.currentThread().getName() + " 已整理");
this.phaser.arrive();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结
如果一个相互合作的线程集满足上面这些行为模式之一,那么应该直接使用合适的同步组件而不要去尝试用使用锁与条件的集合。
参考来源《JAVA核心技术卷I》
原创不易欢迎点赞,如有错误请不吝指点...
转载请标明出处:https://blog.csdn.net/chen1250/article/details/120363513