文章目录
一、CountDownLatch(减法计数器)
什么是CountDownLatch?
A CountDownLatch是一种通用的同步工具,可用于多种用途。 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
CountDownLatch 常用方法
1.CountDownLatch(int count)
构造一个以给定计数 CountDownLatch CountDownLatch。
2.public void await() throws InterruptedException
如果当前计数为零,则此方法立即返回。
如果当前计数大于零,则当前线程将被禁用以进行线程调度,并处于休眠状态,直至发生两件事情之一:
由于countDown()方法的调用,计数达到零; 要么 一些其他线程interrupts当前线程。
3.public boolean await(long timeout, TimeUnit unit)throws InterruptedException
导致当前线程等到锁存器向下计数到零,除非线程为interrupted ,否则指定的等待时间过去。
如果当前计数为零,则此方法将立即返回值为true 。如果当前计数大于零,则当前线程将被禁用以进行线程调度,并处于休眠状态,直至发生三件事情之一:
由于countDown()方法的调用,计数达到零; 要么 一些其他线程interrupts当前线程; 要么 指定的等待时间过去了。 如果计数达到零,则方法返回值为true 。
public void countDown()
减少锁存器的计数,如果计数达到零,释放所有等待的线程。
如果当前计数大于零,则它将递减。 如果新计数为零,则所有等待的线程都将被重新启用以进行线程调度。
如果当前计数等于零,那么没有任何反应。
public long getCount()
返回当前计数。
CountDownLatch原理
CountDownLatch是通过一个计数器来实现的,计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已完成任务,然后在闭锁上等待的线程就可以恢复执行任务。 如下图所示:
CountDownLatch实例
public class TestCountDownLatch {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(2);
for (int i = 1; i <= 2; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();//计算减一
},String.valueOf(i)).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("close Down");
}
}
测试结果:
1
2
close Down
二、Semaphore(信号量)
什么是Semaphore?
Semaphore是一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。 信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。
Semaphore实现的功能就类似有3个停车位,假如有6个人要停车,那么同时只能停多少辆车?同时只能有3个人能够占用,当3个人中 的任何一个人开车离开后,其中等待的另外3个人中又有一个人可以来停车了。另外等待的2个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
下面我们用代码来模拟一下这个场合:
public class TestSamphere {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获得车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}).start();
}
}
}
测试结果:
Thread-0获得车位
Thread-3获得车位
Thread-1获得车位
Thread-0离开车位
Thread-1离开车位
Thread-3离开车位
Thread-4获得车位
Thread-5获得车位
Thread-2获得车位
Thread-5离开车位
Thread-2离开车位
Thread-4离开车位
Semaphore常用方法
构造方法
Semaphore(int permits)
创建一个 Semaphore与给定数量的许可证和非公平公平设置。
Semaphore(int permits, boolean fair)
创建一个 Semaphore与给定数量的许可证和给定的公平设置。
acquire() 方法
从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。
acquire(int permits)
从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted
release() 方法
释放许可证,将其返回到信号量。
release(int permits)
释放给定数量的许可证,将其返回到信号量。
getQueueLength()
返回等待获取的线程数的估计。
availablePermits()
返回此信号量中当前可用的许可数。
isFair()
如果此信号量的公平设置为真,则返回 true 。
tryAcquire()
从这个信号量获得许可证,只有在调用时可以使用该许可证。
tryAcquire(int permits)
从这个信号量获取给定数量的许可证,只有在调用时全部可用。
tryAcquire(int permits, long timeout, TimeUnit unit)
从该信号量获取给定数量的许可证,如果在给定的等待时间内全部可用,并且当前线程尚未 interrupted 。
tryAcquire(long timeout, TimeUnit unit)
如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。
三、CyclicBarrier(加法计数器)
什么是CyclicBarrier
栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
CyclicBarrier常用方法
CyclicBarrier(int parties)
创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。
CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
await()
等待所有 parties已经在这个障碍上调用了 await 。
await(long timeout, TimeUnit unit)
等待所有 parties已经在此屏障上调用 await ,或指定的等待时间过去。
getNumberWaiting()
返回目前正在等待障碍的各方的数量。
isBroken()
查询这个障碍是否处于破碎状态。
reset()
将屏障重置为初始状态。
我们再来看一个实例:
public class TestCyclicBarrier {
public static void main(String[] args) {
//集齐七颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
final int temp=i;//中间变量
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集第"+temp+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
测试结果:
Thread-0收集第1颗龙珠
Thread-3收集第4颗龙珠
Thread-4收集第5颗龙珠
Thread-1收集第2颗龙珠
Thread-5收集第6颗龙珠
Thread-2收集第3颗龙珠
Thread-6收集第7颗龙珠
召唤神龙成功
CyclicBarrier和CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。