Semaphore
信号量,用来控制对有限资源的访问数量。场景:有限资源的使用限制
构造函数
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
permits表示初始可用的资源,注意,只是初始值为permits,并不是资源的最大数,通过释放资源的操作可以使可用资源数量超过初始值。
fair等待资源的线程是否采用公平策略获取锁,true即是,先来先得。(公平锁)
acquire()
//申请一个资源
public void acquire() throws InterruptedException
//申请permits
public void acquire(int permits) throws InterruptedException
申请资源,当申请的资源>现有可用资源时,申请资源的线程将被阻塞,直到有可用资源或者申请线程被打断,若线程被打断,则抛出InterruptedException异常
acquireUninterruptibly():申请资源,阻塞时不会打断,一直等待申请资源
public void acquireUninterruptibly()
public void acquireUninterruptibly(int permits)
申请资源,当申请的资源>现有可用资源时,申请资源的线程将被阻塞,直到有可用资源。并且在此过程中线程不会被打算,将一直等待可用资源。tryAcquire()
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException
public boolean tryAcquire(int permits)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
- 第一个函数申请一个资源,如果当前有可用资源,立即返回true,否则立即返回false;
- 第二个函数申请一个资源,指定一个超时时间,如果当前可以资源数量足够,立即返回true,否则最多等待给定的时间,如果时间到还是未能获取资源,则返回false;如果等待过程中线程被打断,抛出InterruptedException异常;
- 和1一样,只是申请permits个资源;
- 和2一样,只是申请permits个资源。
release()
public void release()
public void release(int permits)
第一个函数释放一个资源,第二个函数释放permits个资源。注意,就算现在可用的资源数为0,此时调用release(),将使资源数为1。
availablePermits():获取当前可用资源数
public int availablePermits()
drainPermits():返回当前可用资源数,并清零可用资源数
public int drainPermits()
reducePermits():禁止某些资源不可用
protected void reducePermits(int reduction)
reduction表示禁止的数量,如果reduction小于零,则抛出IllegalArgumentException异常。注意函数是protected的,同包下才能访问,我感觉平时不常用。
例:
public class TestSemaphore {
public static void main(String[] args) throws Exception {
Semaphore wc = new Semaphore(3, true); // 3个坑位
for (int i = 1; i <= 6; i++) {
Thread t = new Thread(new Person("第" + i + "个人", wc));
t.start();
Thread.sleep(new Random().nextInt(300));
}
}
static class Person implements Runnable {
private String name;
private Semaphore wc;
public Person(String name, Semaphore wc) {
this.name = name;
this.wc = wc;
}
public void run() {
System.out.print(name + ":憋死老子了!");
if (wc.availablePermits() > 0) {
System.out.println("天助我也,有坑位!");
} else {
System.out.println("卧槽,没坑位了,等会儿吧...");
}
try {
wc.acquire(); //申请坑位
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":终于轮到我了,拉屎就是爽!");
try {
Thread.sleep(new Random().nextInt(1000)); // 模拟上厕所时间。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":拉完了,好臭!");
wc.release();
}
}
}
注意
release函数和acquire并没有要求一定是同一个线程都调用,可以A线程申请资源,B线程释放资源;
调用release函数之前并没有要求一定要先调用acquire函数。
CountDownLatch
countDownLatch是一个同步工具类,join的增强版。允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
//构造函数,初始化计数器值为count,count只能被设置一次
public CountDownLatch(int count);
//调用await()方法的线程会被挂起,直到count值为0才继续执行
public void await() throws InterruptedException;
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(longtimeout, TimeUnit unit)throws InterruptedException;
//将count值减1
public void countDown();
它是通过控制计数器的值来达到等待的目的。当计数器的值>0时,调用countDownLatch.await()会阻塞当前线程,直到其他线程调用countDownLatch.countDown()将计数器的值减到0时,阻塞线程将被唤醒。计数器的值>0时调用await()方法不会阻塞当前线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
例
public static void testCountDownLatch(){
int threadCount = 10;
final CountDownLatch latch = new CountDownLatch(threadCount);//初始化计数器值
for(int i=0; i< threadCount; i++){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getId() + "开始出发");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getId() + "已到达终点");
latch.countDown();//将计数器值减1
}
}).start();
}
try {
latch.await();//计数器>0时主线程被阻塞,直到=0被唤醒。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10个线程已经执行完毕!开始计算排名");
}
join与countDownLatch区别
join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait,代码片段如下,wait(0)表示永远等待下去。
1 | while (isAlive()) { |
2 | wait( 0 ); |
3 | } |
直到join线程中止后,线程的this.notifyAll会被调用,调用notifyAll是在JVM里实现的,所以JDK里看不到。
join方法必须要待被等待线程执行结束后,等待线程才可以继续执行。而countDownLatch没有这个线程,只要count减小到0,不管被等待线程是否执行结束,等待线程都可以继续执行(被唤醒,进入可执行状态)。
CyclicBarrier
可循环使用的屏障。它允许一组线程在到达某个栅栏点(await())互相等待,发生阻塞,直到最后一个线程到达栅栏点(到达栅栏点的数量=指定的栅栏数),栅栏才会打开,处于阻塞状态的线程恢复继续执行。它适用于一组线程之间需要互相等待的情况。CyclicBarrier字面理解是循环的栅栏,之所以称之为循环的是因为在等待线程释放后,该栅栏还可以复用。
主要方法:
//构造函数,parties为到达屏障的线程数,当指定的线程值都到达栅栏点时,栅栏打开,线程恢复。如果parties<执行await()的线程数,被阻塞的线程将永远阻塞,因为无法达到指定指定屏障数
public CyclicBarrier(int parties)
//构造函数,同上,barrierAction为到达指定屏障数之后执行的动作。到达指定屏障数后限制性该Runnable,后再继续执行被唤醒的线程
public CyclicBarrier(int parties, Runnable barrierAction)
//到达屏障,当前线程被阻塞,直到指定数量的线程都到达栅栏点时恢复执行。返回值为在当前线程到达时,还需要wait的线程数
public int await() throws InterruptedException, BrokenBarrierException
//到达屏障,等待指定时间仍不能恢复将抛出异常
public int await(long timeout, TimeUnit unit)
//重置屏障数为初始状态
public void reset()
主要工作原理,通过构造函数创建一个指定屏障数的屏障类,在各线程中调用await(),调用后当前线程将被阻塞,直到,调用的次数到指定屏障数后,所有阻塞的线程将恢复继续执行。
关于两个wait()方法的补充
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
* 最后一个线程到达;或者
* 其他某个线程中断当前线程;或者
* 其他某个线程中断另一个等待线程;或者
* 其他某个线程在等待 barrier 时超时;或者
* 其他某个线程在此 barrier 上调用 reset()。
如果当前线程:
* 在进入此方法时已经设置了该线程的中断状态;或者
* 在等待时被中断
则抛出 InterruptedException,并且清除当前线程的已中断状态。
如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,抑或任意一个线程正处于等待状态,
则抛出 BrokenBarrierException 异常。
如果任何线程在等待时被中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。
如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,则在允许其他线程继续运行之前,
当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
返回:
到达的当前线程的序列号,其中,getParties() - 1 指示将到达的第一个线程,零指示最后一个到达的线程
抛出:
InterruptedException - 如果当前线程在等待时被中断
BrokenBarrierException - 如果另一个 线程在当前线程等待时被中断或超时,或者重置了 barrier,
或者在调用 await 时 barrier 被损坏,抑或由于异常而导致屏障操作(如果存在)失败。
注意1:返回是"到达的当前线程的序列号",但是本质表示在当前线程到达时,还需要await几个线程。
barrier.await() == barrier.getParties() - 1 表示是第一个到达的线程,barrier.await() == 0表示这是最后一个到达的线程,当然此时它已经跨过了屏障。 至于线程是第几个跨越障碍的线程,这个不清楚,应该和线程的调度有关。
public int await(long timeout,TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
* 最后一个线程到达;或者
* 超出指定的超时时间;或者
* 其他某个线程中断当前线程;或者
* 其他某个线程中断另一个等待线程;或者
* 其他某个线程在等待 barrier 时超时;或者
* 其他某个线程在此 barrier 上调用 reset()。
如果当前线程:
* 在进入此方法时已经设置了该线程的中断状态;或者
* 在等待时被中断
则抛出 InterruptedException,并且清除当前线程的已中断状态。
如果超出指定的等待时间,则抛出 TimeoutException 异常。如果该时间小于等于零,则此方法根本不会等待。
如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,
抑或任意一个线程正处于等待状态,则抛出 BrokenBarrierException 异常。
如果任何线程在等待时被中断,则其他所有等待线程都将抛出 BrokenBarrierException,并将屏障置于损坏状态。
如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,
则在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,
则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
参数:
timeout - 等待 barrier 的时间
unit - 超时参数的时间单位
返回:
到达的当前线程的索引,其中,索引 getParties() - 1 指示第一个将要到达的线程,零指示最后一个到达的线程
抛出:
InterruptedException - 如果当前线程在等待时被中断
TimeoutException - 如果超出了指定的超时时间
BrokenBarrierException - 如果另一个 线程在当前线程等待时被中断或超时,或者重置了 barrier,
或者调用 await 时 barrier 被损坏,抑或由于异常而导致屏障操作(如果存在)失败。
例
public class BarrierDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
final CyclicBarrier barrier = new CyclicBarrier(5);
for (int i = 0; i < 5; i++) {
service.execute(new Player("玩家" + i, barrier));
}
service.shutdown();
}
}
public class Player implements Runnable {
private final String name;
private final CyclicBarrier barrier;
public Player(String name, CyclicBarrier barrier) {
this.name = name;
this.barrier = barrier;
}
public void run() {
try {
TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
System.out.println(name + "已准备,等待其他玩家准备...");
barrier.await();
TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
System.out.println(name + "已加入游戏");
} catch (InterruptedException e) {
System.out.println(name + "离开游戏");
} catch (BrokenBarrierException e) {
System.out.println(name + "离开游戏");
}
}
}
结果
玩家0已准备,等待其他玩家准备...
玩家2已准备,等待其他玩家准备...
玩家1已准备,等待其他玩家准备...
玩家4已准备,等待其他玩家准备...
玩家3已准备,等待其他玩家准备...
玩家4已加入游戏
玩家1已加入游戏
玩家0已加入游戏
玩家3已加入游戏
玩家2已加入游戏
CyclicBarrier与CountDownLatch区别
CountDownLatch | CyclicBarrier |
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
无法重置 | 计数达到指定值时,计数置为0重新开始,也可用过调用reset()方法重置 |
调用countDown()方法计数减一,线程继续执行,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
某个或某些线程等待其他线程 | 线程间互相等待 |
Semaphore参考:https://blog.csdn.net/hechurui/article/details/49508439
http://www.importnew.com/15731.html
https://www.cnblogs.com/bqcoder/p/6089101.html
https://blog.csdn.net/hudashi/article/details/7004199
https://blog.csdn.net/yin380697242/article/details/53313622 例子
CyclicBarrier与CountDownLatch区别参考: