一文搞懂四种同步工具类
开发者全社区
共 2646字,需浏览 6分钟
·
2021-11-10 02:18
来源:juejin.cn/post/6844903958360621064
CountDownLatch
解释:
代码:
public class TestCountDownLatch {
public static void main(String[] args) {
// 需要等待两个线程,所以传入参数为2
CountDownLatch latch = new CountDownLatch(2);
// 该线程运行1秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号选手准备就绪!用时1秒!");
latch.countDown();
}
}).start();
// 该线程运行3秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号选手准备就绪!用时3秒!");
latch.countDown();
}
}).start();
try {
System.out.println("请1号选手和2号选手各就各位!");
// 主线程在此等待两个线程执行完毕之后继续执行
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 两个线程执行完毕后,主线程恢复运行
System.out.println("裁判发枪,1号选手和2号选手开跑!");
}
}
运行结果:
请1号选手和2号选手各就各位!
1号选手准备就绪!用时1秒!
2号选手准备就绪!用时3秒!
裁判发枪,1号选手和2号选手开跑!
如果去掉CountDownLatch的效果呢?运行结果就会变成如下:
请1号选手和2号选手各就各位!
裁判发枪,1号选手和2号选手开跑!
1号选手准备就绪!用时1秒!
2号选手准备就绪!用时3秒!
裁判就会在选手还未准备就绪的时候开发令枪,这就乱套了。
其实CountDownLatch一个最简单的用处就是计算多线程执行完毕时的时间。像刚才的例子当中两个线程并行执行了共花费了3秒钟。
CyclicBarrier
解释:
我们举一个例子,两个选手进行比赛,一共进行三轮比赛。
代码:
public class TestCyclicBarrier {
// 1号选手跑的轮数
public static int countA = 1;
// 2号选手跑的轮数
public static int countB = 1;
public static void main(String[] args) {
// 填入2,代表2个线程互相等待
CyclicBarrier barrier = new CyclicBarrier(2);
new Thread(new Runnable() {
@Override
public void run() {
// 一共跑三轮
for (int i = 0; i < 3; i++) {
System.out.println("1号选手开始跑!当前第" + countA++ + "轮比赛!");
// 1号选手跑得慢,每次跑三秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("1号选手抵达终点!");
// 调用等待方法,在此等待其他选手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 一共等待三轮
for (int i = 0; i < 3; i++) {
System.out.println("2号选手开始跑!当前第" + countB++ + "轮比赛!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("2号选手抵达终点!");
// 调用等待方法,在此等待其他选手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
运行结果:
1号选手开始跑!当前第1轮比赛!
2号选手开始跑!当前第1轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第2轮比赛!
2号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第3轮比赛!
2号选手开始跑!当前第3轮比赛!
2号选手抵达终点!
1号选手抵达终点!
每轮比赛1号选手和2号选手都会回到起跑线互相等待,再开启下一轮比赛。
搜索公众号互联网架构师后台回复“2T”,获取一份惊喜礼包。
如果不加CyclicBarrier呢?
1号选手开始跑!当前第1轮比赛!
2号选手开始跑!当前第1轮比赛!
2号选手抵达终点!
2号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
2号选手开始跑!当前第3轮比赛!
1号选手抵达终点!
1号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第3轮比赛!
1号选手抵达终点!
此时2号选手就直接跑完三轮比赛,不等1号选手了。
Semaphore
Semaphore英文的字面意思是信号量。它的工作机制是每个线程想要获取运行的机会的话,都必须获取到信号量。acquire()方法阻塞的获取信号量,release()释放信号量。
举个例子,假设我们去迪士尼游玩,但是迪士尼担心游客很多的话,影响大家的游玩体验,于是规定每个小时只能卖出两张门票。这样就可以控制在游乐园当中的游客数量了。
代码:
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
System.out.println("顾客在售票处等候中");
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
try {
Thread.sleep(500);
// 等待出票
semaphore.acquire();
System.out.println("顾客拿到门票入场!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
// 等待一小时再发门票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 一次性发出两张门票
System.out.println("售票处第" + (i + 1) + "小时售出两张票!");
semaphore.release();
semaphore.release();
}
}
}).start();
System.out.println("售票处开始售票!");
}
}
运行结果:
顾客在售票处等候中...
售票处开始售票!
售票处第1小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第2小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第3小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
Exchanger
解释:
Exchanger提供了让两个线程互相交换数据的同步点。Exchanger有点像2个线程的CyclicBarrier,线程之间都是互相等待,区别在于Exchanger多了交换的操作。
举个例子好比以前玩网游的时候,买家和卖家必须走到地图上同一地点面对面进行交易一样,一手交钱一手交装备。
代码:
public class TestExchanger {
public static void main(String[] args) {
Exchanger exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
String weapon = "装备";
System.out.println("我是卖家,我带着" + weapon + "过来了!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖家到达地图上交易地点");
try {
System.out.println("我是卖家,换回了" + exchanger.exchange(weapon));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String money = "一万游戏币";
System.out.println("我是买家,我带着" + money + "过来了");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买家到达地图上交易地点");
try {
System.out.println("我是买家,换回了" + exchanger.exchange(money));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
我是卖家,我带着装备过来了!
我是买家,我带着一万游戏币过来了
卖家达到交易地点
买家到达交易地点
我是买家,换回了装备
我是卖家,换回了一万游戏币
PS:如果觉得我的分享不错,欢迎大家随手点赞、转发、在看。
来源:juejin.cn/post/6844903958360621064 CountDownLatch
解释:
代码:
public class TestCountDownLatch {
public static void main(String[] args) {
// 需要等待两个线程,所以传入参数为2
CountDownLatch latch = new CountDownLatch(2);
// 该线程运行1秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号选手准备就绪!用时1秒!");
latch.countDown();
}
}).start();
// 该线程运行3秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号选手准备就绪!用时3秒!");
latch.countDown();
}
}).start();
try {
System.out.println("请1号选手和2号选手各就各位!");
// 主线程在此等待两个线程执行完毕之后继续执行
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 两个线程执行完毕后,主线程恢复运行
System.out.println("裁判发枪,1号选手和2号选手开跑!");
}
}运行结果:
请1号选手和2号选手各就各位!
1号选手准备就绪!用时1秒!
2号选手准备就绪!用时3秒!
裁判发枪,1号选手和2号选手开跑!如果去掉CountDownLatch的效果呢?运行结果就会变成如下:
请1号选手和2号选手各就各位!
裁判发枪,1号选手和2号选手开跑!
1号选手准备就绪!用时1秒!
2号选手准备就绪!用时3秒!裁判就会在选手还未准备就绪的时候开发令枪,这就乱套了。
其实CountDownLatch一个最简单的用处就是计算多线程执行完毕时的时间。像刚才的例子当中两个线程并行执行了共花费了3秒钟。CyclicBarrier
解释:
我们举一个例子,两个选手进行比赛,一共进行三轮比赛。
代码:
public class TestCyclicBarrier {
// 1号选手跑的轮数
public static int countA = 1;
// 2号选手跑的轮数
public static int countB = 1;
public static void main(String[] args) {
// 填入2,代表2个线程互相等待
CyclicBarrier barrier = new CyclicBarrier(2);
new Thread(new Runnable() {
@Override
public void run() {
// 一共跑三轮
for (int i = 0; i < 3; i++) {
System.out.println("1号选手开始跑!当前第" + countA++ + "轮比赛!");
// 1号选手跑得慢,每次跑三秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("1号选手抵达终点!");
// 调用等待方法,在此等待其他选手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 一共等待三轮
for (int i = 0; i < 3; i++) {
System.out.println("2号选手开始跑!当前第" + countB++ + "轮比赛!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("2号选手抵达终点!");
// 调用等待方法,在此等待其他选手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
}
}运行结果:
1号选手开始跑!当前第1轮比赛!
2号选手开始跑!当前第1轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第2轮比赛!
2号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第3轮比赛!
2号选手开始跑!当前第3轮比赛!
2号选手抵达终点!
1号选手抵达终点!每轮比赛1号选手和2号选手都会回到起跑线互相等待,再开启下一轮比赛。
搜索公众号互联网架构师后台回复“2T”,获取一份惊喜礼包。
如果不加CyclicBarrier呢?
1号选手开始跑!当前第1轮比赛!
2号选手开始跑!当前第1轮比赛!
2号选手抵达终点!
2号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
2号选手开始跑!当前第3轮比赛!
1号选手抵达终点!
1号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第3轮比赛!
1号选手抵达终点!此时2号选手就直接跑完三轮比赛,不等1号选手了。
Semaphore
Semaphore英文的字面意思是信号量。它的工作机制是每个线程想要获取运行的机会的话,都必须获取到信号量。acquire()方法阻塞的获取信号量,release()释放信号量。
举个例子,假设我们去迪士尼游玩,但是迪士尼担心游客很多的话,影响大家的游玩体验,于是规定每个小时只能卖出两张门票。这样就可以控制在游乐园当中的游客数量了。
代码:
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
System.out.println("顾客在售票处等候中");
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
try {
Thread.sleep(500);
// 等待出票
semaphore.acquire();
System.out.println("顾客拿到门票入场!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
// 等待一小时再发门票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 一次性发出两张门票
System.out.println("售票处第" + (i + 1) + "小时售出两张票!");
semaphore.release();
semaphore.release();
}
}
}).start();
System.out.println("售票处开始售票!");
}
}运行结果:
顾客在售票处等候中...
售票处开始售票!
售票处第1小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第2小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第3小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!Exchanger
解释:
Exchanger提供了让两个线程互相交换数据的同步点。Exchanger有点像2个线程的CyclicBarrier,线程之间都是互相等待,区别在于Exchanger多了交换的操作。
举个例子好比以前玩网游的时候,买家和卖家必须走到地图上同一地点面对面进行交易一样,一手交钱一手交装备。
代码:
public class TestExchanger {
public static void main(String[] args) {
Exchangerexchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
String weapon = "装备";
System.out.println("我是卖家,我带着" + weapon + "过来了!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖家到达地图上交易地点");
try {
System.out.println("我是卖家,换回了" + exchanger.exchange(weapon));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String money = "一万游戏币";
System.out.println("我是买家,我带着" + money + "过来了");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买家到达地图上交易地点");
try {
System.out.println("我是买家,换回了" + exchanger.exchange(money));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}我是卖家,我带着装备过来了!
我是买家,我带着一万游戏币过来了
卖家达到交易地点
买家到达交易地点
我是买家,换回了装备
我是卖家,换回了一万游戏币PS:如果觉得我的分享不错,欢迎大家随手点赞、转发、在看。
评论