三种同步工具类
Java为我们提供了三种同步工具类,以便更好的控制线程间的通信,如下:
①、闭锁
②、信号量
③、栅栏
一、闭锁
闭锁可以延迟线程的进度直到其达到终止状态。可以用来确保某些活动在其他活动都完成之后再新执行,例如:
①、确保计算机所需要的资源都被初始化之后在继续执行。
②、确保某个服务再起所依赖的服务都完成之后再执行。
③、等待某个操作的所有参与者都参与进来之后在执行下一步的操作(王者荣耀游戏的所有玩家都准备就绪才开始游戏)。
CountDownLatch实现闭锁
CountDownLatch是一种灵活的闭锁实现,它能够使一个或多个线程等待一组事件的发生。CountDownLatch初始化参数为一个整数,表示需要等待的事件的数量,countDown()方法其实就是一个递减计数器,await()方法表示的是等待计数器的值为0,当等待计数器的值为0的时候,表示的是所有需要等待的事件都已经完成了;如果计数器的值非0,则await()方法会一直阻塞到等待计数器值为0。下面是一个简单的实例:
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(5);
new Thread(new Runnable() {
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老师们都走了,我可以走了");
}
}).start();
for (int i = 0; i < 5; i++){
new Thread(new Runnable() {
public void run() {
System.out.println("老师xxx走了");
countDownLatch.countDown();
}
}).start();
}
}
}
运行结果如下:
老师xxx走了
老师xxx走了
老师xxx走了
老师xxx走了
老师xxx走了
老师们都走了,我可以走了
总结:CountDownLatch首先会初始化来设置需要等待的线程数,然后在需要被等待的线程执行完成之后调用countDown()方法来让等待计数器的值减一,在需要等待其他线程的线程中调用await()方法,让此线程阻塞到等待计数器的值为0。
信号量
信号量其实就是控制可以同时访问的线程的个数,它维护着一组虚拟的 “permit”,只有拥有permit的线程才能够执行,否则不能执行。
Semaphore实现信号量
使用Semaphore的大致流程如下:
①、当调用acquire()方法时会获取到一个“permit”,permit的个数对应减一;
②、当调用release方法时会归还一个“permit”,permit加一。
示例如下:
public class TestSamephore {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(10);
for (int i = 1; i <= 50; i++){
final int nowI = i;
new Thread(new Runnable() {
public void run() {
try {
semaphore.acquire();
System.out.println("编号为:" +nowI + " 的顾客在挑选商品......");
Thread.sleep(1000);
System.out.println("编号为:" +nowI + " 的顾客挑选完成!!!!!!");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
运行结果:
编号为:1 的顾客在挑选商品......
编号为:43 的顾客在挑选商品......
编号为:2 的顾客在挑选商品......
编号为:47 的顾客在挑选商品......
编号为:50 的顾客在挑选商品......
编号为:48 的顾客在挑选商品......
编号为:44 的顾客在挑选商品......
编号为:42 的顾客在挑选商品......
编号为:45 的顾客在挑选商品......
编号为:3 的顾客在挑选商品......
编号为:1 的顾客挑选完成!!!!!!
编号为:4 的顾客在挑选商品......
编号为:43 的顾客挑选完成!!!!!!
编号为:16 的顾客在挑选商品......
编号为:2 的顾客挑选完成!!!!!!
编号为:7 的顾客在挑选商品......
编号为:47 的顾客挑选完成!!!!!!
编号为:13 的顾客在挑选商品......
编号为:50 的顾客挑选完成!!!!!!
编号为:12 的顾客在挑选商品......
编号为:3 的顾客挑选完成!!!!!!
编号为:45 的顾客挑选完成!!!!!!
编号为:17 的顾客在挑选商品......
编号为:42 的顾客挑选完成!!!!!!
编号为:11 的顾客在挑选商品......
编号为:44 的顾客挑选完成!!!!!!
编号为:15 的顾客在挑选商品......
编号为:48 的顾客挑选完成!!!!!!
编号为:21 的顾客在挑选商品......
编号为:5 的顾客在挑选商品......
编号为:4 的顾客挑选完成!!!!!!
编号为:10 的顾客在挑选商品......
编号为:7 的顾客挑选完成!!!!!!
编号为:16 的顾客挑选完成!!!!!!
编号为:14 的顾客在挑选商品......
编号为:38 的顾客在挑选商品......
编号为:13 的顾客挑选完成!!!!!!
编号为:6 的顾客在挑选商品......
编号为:5 的顾客挑选完成!!!!!!
编号为:11 的顾客挑选完成!!!!!!
编号为:17 的顾客挑选完成!!!!!!
编号为:12 的顾客挑选完成!!!!!!
编号为:18 的顾客在挑选商品......
编号为:40 的顾客在挑选商品......
编号为:20 的顾客在挑选商品......
编号为:15 的顾客挑选完成!!!!!!
编号为:21 的顾客挑选完成!!!!!!
编号为:25 的顾客在挑选商品......
编号为:37 的顾客在挑选商品......
编号为:35 的顾客在挑选商品......
编号为:10 的顾客挑选完成!!!!!!
编号为:31 的顾客在挑选商品......
编号为:14 的顾客挑选完成!!!!!!
编号为:36 的顾客在挑选商品......
编号为:38 的顾客挑选完成!!!!!!
编号为:34 的顾客在挑选商品......
编号为:6 的顾客挑选完成!!!!!!
编号为:19 的顾客在挑选商品......
编号为:20 的顾客挑选完成!!!!!!
编号为:18 的顾客挑选完成!!!!!!
编号为:40 的顾客挑选完成!!!!!!
编号为:33 的顾客在挑选商品......
编号为:22 的顾客在挑选商品......
编号为:27 的顾客在挑选商品......
编号为:25 的顾客挑选完成!!!!!!
编号为:37 的顾客挑选完成!!!!!!
编号为:30 的顾客在挑选商品......
编号为:35 的顾客挑选完成!!!!!!
编号为:23 的顾客在挑选商品......
编号为:26 的顾客在挑选商品......
编号为:31 的顾客挑选完成!!!!!!
编号为:32 的顾客在挑选商品......
编号为:36 的顾客挑选完成!!!!!!
编号为:29 的顾客在挑选商品......
编号为:19 的顾客挑选完成!!!!!!
编号为:34 的顾客挑选完成!!!!!!
编号为:24 的顾客在挑选商品......
编号为:28 的顾客在挑选商品......
编号为:22 的顾客挑选完成!!!!!!
编号为:33 的顾客挑选完成!!!!!!
编号为:39 的顾客在挑选商品......
编号为:41 的顾客在挑选商品......
编号为:27 的顾客挑选完成!!!!!!
编号为:49 的顾客在挑选商品......
编号为:23 的顾客挑选完成!!!!!!
编号为:30 的顾客挑选完成!!!!!!
编号为:26 的顾客挑选完成!!!!!!
编号为:9 的顾客在挑选商品......
编号为:8 的顾客在挑选商品......
编号为:46 的顾客在挑选商品......
编号为:32 的顾客挑选完成!!!!!!
编号为:29 的顾客挑选完成!!!!!!
编号为:28 的顾客挑选完成!!!!!!
编号为:24 的顾客挑选完成!!!!!!
编号为:39 的顾客挑选完成!!!!!!
编号为:41 的顾客挑选完成!!!!!!
编号为:49 的顾客挑选完成!!!!!!
编号为:46 的顾客挑选完成!!!!!!
编号为:8 的顾客挑选完成!!!!!!
编号为:9 的顾客挑选完成!!!!!!
每次最多店铺里有是个人在挑选商品。
注意:可以存在以下操作:
Semaphore s = new Samephore(0);
s.release();
s.acquire();
说明:设置为0后是可以release的,然后就可以acquire. 这里设置为0,就是一开始使线程阻塞从而完成其他执行。
三、栅栏
栅栏允许一组线程互相等待,直到到达某个公共屏障点,栅栏可以被重用,而CountDownLatch不能。
CountDownLatch注重的是等待其他线程完成,栅栏注重的是:当线程到达某个状态后,暂停下来等待其他线程,所有线程均到达以后,继续执行。
简单的例子:
public class TestCyclicBarrier {
public static void main(String[] args) {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
for (int i = 1; i <= 5; i++){
final int nowI = i;
new Thread(new Runnable() {
public void run() {
System.out.println(nowI + " 到达饭店");
try {
cyclicBarrier.await();
System.out.println(nowI + " 和四个朋友在吃饭");
cyclicBarrier.await();
System.out.println("五个人一起聊天");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
运行结果:
1 到达饭店
2 到达饭店
3 到达饭店
4 到达饭店
5 到达饭店
5 和四个朋友在吃饭
4 和四个朋友在吃饭
1 和四个朋友在吃饭
3 和四个朋友在吃饭
2 和四个朋友在吃饭
五个人一起聊天
五个人一起聊天
五个人一起聊天
五个人一起聊天
五个人一起聊天
从运行结果看出:当有人到了之后就回等待,直到五个人全部到了之后才开始吃饭,也就是先到的等待未到的全部到达之后在进行下一步。
参考:<< Java 并发编程实战 >>
leetcode:1114. Print in Order(按顺序打印)使用这三个工具类的三种解法:
一:使用CountDownLatch
class Foo {
private CountDownLatch countDownLatch1;
private CountDownLatch countDownLatch2;
public Foo() {
countDownLatch1 = new CountDownLatch(1);
countDownLatch2 = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
countDownLatch1.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
countDownLatch1.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
countDownLatch2.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
countDownLatch2.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
二:使用Semaphore
class Foo {
private Semaphore s1;
private Semaphore s2;
private Semaphore s3;
public Foo() {
s1 = new Semaphore(1);
s2 = new Semaphore(0);
s3 = new Semaphore(0);
}
public void first(Runnable printFirst) throws InterruptedException {
s1.acquire();
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
s2.release();
}
public void second(Runnable printSecond) throws InterruptedException {
s2.acquire();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
s3.release();
}
public void third(Runnable printThird) throws InterruptedException {
s3.acquire();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
三:使用CyclicBarrier
class Foo {
private CyclicBarrier c1;
private CyclicBarrier c2;
public Foo() {
c1 = new CyclicBarrier(2);
c2 = new CyclicBarrier(2);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
try{
c1.await();
}catch(Exception e){
}
}
public void second(Runnable printSecond) throws InterruptedException {
try{
c1.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
c2.await();
}catch(Exception e){
}
}
public void third(Runnable printThird) throws InterruptedException {
try{
c2.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}catch(Exception e){
}
}
}
四:使用等待唤醒机制:
class Foo {
private boolean f1;
private boolean f2;
public Foo() {
f1 = false;
f2 = false;
}
public void first(Runnable printFirst) throws InterruptedException {
synchronized(this){
printFirst.run();
f1 = true;
this.notifyAll();
}
// printFirst.run() outputs "first". Do not change or remove this line.
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized(this){
while(!f1)
this.wait();
printSecond.run();
f2 = true;
this.notifyAll();
}
// printSecond.run() outputs "second". Do not change or remove this line.
}
public void third(Runnable printThird) throws InterruptedException {
synchronized(this){
while(!f2)
this.wait();
printThird.run();
this.notifyAll();
}
// printThird.run() outputs "third". Do not change or remove this line.
}
}