文章目录
- 1、CountDownLatch减少计数
- 2、CyclicBarrier循环栅栏
- 3、Semaphore信号灯
本篇整理JUC的几个同步辅助类:
- 减少计数:CountDownLatch
- 循环栅栏:CyclicBarrier
- 信号灯:Semaphore
1、CountDownLatch减少计数
案例:6个同学陆续离开教室后,班长才可以锁门
先不使用CountDownLatch,看下效果:
public class CountDownDemo {
public static void main(String[] args) {
for (int i = 1; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
}
}
运行:这里循环执行6次start,创建6个就绪状态的线程后,main线程继续向下执行,而这6个新线程还要抢夺CPU时间片、执行,因此可能出现其他线程未执行结束,main线程(班长)就执行结束的情况。(先main线程中start创建线程,再抢时间片,抢到后才执行run方法,不要迷)
改进,引入CountDownLatch:
public class CountDownDemo {
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
//计数减一(因为到这儿,新线程的run方法也执行完了)
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//计数器未成0前,让当前线程(main线程)挂起
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
}
}
CountDownLatch类的常用方法
:
- 构造器方法,传入初始的计数
CountDownLatch(int count)
- 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
void await()
- 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
boolean await(long timeout, TimeUnit unit)
- 锁存器计数减一
void countDown()
- 返回当前计数
long getCount()
总结
:
- 定义计数器初始值
- 每次相关操作后,让计数器减一
- 计数器的值没变成0 ,当前线程就一直await,等计数器变成0后,await后面的代码才会继续执行
2、CyclicBarrier循环栅栏
作用
:
让一组线程互相等待,直到达到某个公共的屏障点。且可以在达到这个公共屏障点后(在一组线程中的最后一个线程到达之后,但在释放所有线程之前)执行一个Runnable。注意不是新开一个线程去执行,而是由最后一个进入 barrier 的线程执行。
案例:集齐7颗龙珠才可以召唤神龙
public class CyclicDemo {
//设置固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("集齐7颗龙珠,召唤神龙成功!");
});
//集齐七颗龙珠的过程
for (int i = 1; i <= 7 ; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "星龙珠被收集到了");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
运行:
修改参数,让for循环跑6次(即只创建6个线程去调用await,刻意不让其到达number),而number仍然为7,此时运行,程序一直等待,最后exit -1
CyclicBarrier类的常用方法
:
- 构造方法,传入参与线程的数量,和最后要执行的操作
CyclicBarrier(int parties, Runnable barrierAction)
- 在所有参与线程未调用CyclicBarrier对象的await方法之前,将一直等待,也就是上面演示的达到不了那个number。这些参与的线程,在其最后调用await,就相当于在说我已在当前屏障出等待。而若有参与线程不调用await,就像春游发车前少个人一样,只能整个线程组都等着。
int await()
- 同上,未到屏障点前,所有调用了await的线程(参与线程)就一直等待,除非到了超时时间
await(long timeout, TimeUnit unit)
- 返回当前已到达屏障点的线程的数量(春游实际到上车点的人数)
int getNumberWaiting()
- 返回传入的屏障点(春游报名人数)
int getParties()
总结
:
Barrier,屏障,参与的线程调一次await,即说明该线程已在屏障出等待,屏障点+1,达到这个屏障点,线程组的所有线程才继续往下执行,否则之前调用了await方法的线程就一直处于await等待状态,除非到了指定的超时时间
3、Semaphore信号灯
作用
:
维护一个信号量,这个信号量里有多个许可,拿到许可就执行,没拿到就等着。有点像对象锁了,而和对象锁不同的是,一个对象,一把对象锁,但Semaphore的这个锁(许可)的数量是你自己传入的。
案例:6辆汽车,停三个停车位
车停在车位A,其他车就不能再停在A位了,这就是获取一个许可。车开出去,就是释放一个许可
acquire获取许可证后,其他线程只能一直等待,阻塞状态
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量,三个车位,对应三个许可证
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//抢占许可证
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到了车位");
//设置一个5s以内的随机时间,模拟停车
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + "=====> 离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放许可
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
运行:
Semaphore类的常用方法
:
- 构造方法,传入一个数字,如果把Semaphore类看成我们前面并发编程步骤里的资源类,那这个数字就是设置有几把对象锁
Semaphore(int permits)
- 构造方法重载,可传入一个Boolean,表示是否公平的给许可证,设置false时,是允许新启动的线程插队的,设置为true,则按照先进先出的方式来在等待的线程队列中选择线程,发放许可
Semaphore(int permits, boolean fair)
- 获取许可
void acquire()
- 释放许可
void release()
总结
:
个人理解就是,如果把Semaphore类看成我们前面并发编程步骤里的资源类,那它特殊的地方就是,这个资源类的一个对象有几把对象锁,是我们可以自己在构造方法里设置的,而普通的自定义资源类,想要n把对象锁,就得new上n个对象。
最后,API文档地址:https://tool.oschina.net/apidocs/apidoc?api=jdk-zh