Semaphore、CountDownLatch、CyclicBarrier
Semaphore
概念
Semaphore是Java版的信号量的实现。它通过控制一定数量的允许(permit)的方式,来达到限制通用资源访问的目的。
类似于在等出租车时,为防止过度拥挤,调度员指挥排队等待坐车的人一次只能进5个人,等此5人走后,再放进下一批。
实现示例代码
Semaphore实现示例
类似于上面提到的等车问题的实现。
public class UsualSemaphoreSample {
public static void main(String[] args) {
System.out.println("Action ... Go!");
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new SemaphoreWorker(semaphore));
t.start();
}
}
}
class SemaphoreWorker implements Runnable{
private String name;
private Semaphore semaphore;
public SemaphoreWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
log("is waiting for a permit!");
semaphore.acquire();
log("acquire a permit");
log("executed!(执行)");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("released a permit!");
semaphore.release();
}
}
private void log(String msg) {
if (name == null)
name = Thread.currentThread().getName();
System.out.println(name +" "+ msg);
}
}
运行结果:
上述代码只是Semaphore的典型示例,实现逻辑是:线程试图获得许可,得到许可后可进行任务,然后释放许可,这时其他线程就已经可以获得许可进入任务了,从运行结果来看,我们Semaphore的允许机制对工作线程的限制。
想要实现满5人再发车的情况还需要进行代码修改。
符合等出租车的示例代码
public class AbnormalSemaphoreSample {
public static void main(String[] args) throws InterruptedException{
Semaphore semaphore = new Semaphore(0);
for (int i = 0; i < 10; i ++) {
Thread t = new Thread(new MyWorker(semaphore));
t.start();
}
System.out.println("Action ... GO!");
semaphore.release(5);
System.out.println("Waiting for permit off");
while (semaphore.availablePermits() != 0)
Thread.sleep(100L);
System.out.println("Action ... Go ... Again!");
semaphore.release(5);
}
}
class MyWorker implements Runnable {
private Semaphore semaphore;
public MyWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Executed!(执行)");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
注意:
以上代码只是为了演示功能,很多使用方法是不建议在工程中实用的,例如使用sleep来协调任务执行,而且使用轮询调用 availablePermit() 方法来检测信号量的获取情况。
这都很低效且脆弱,通常只会用在测试或诊断场景中。
总结
Semaphore就是一个计数器,基本逻辑就是acquire和release,没有太过复杂的同步逻辑。
CountDownLatch 和 CyclicBarrier
二者区别
- CountDownLatch 是不可以重置的,所以不可复用;CyclicBarrier没有这种限制,可以重用。
- CountDownLatch 的基本造作组合是countDown 和 await 。调用await 的线程阻塞等待countDown 足够的次数,不管你是在一个线程里还是多个线程中countDown,只要次数足够即可。可以说CountDownLatch操作的是 事件。
- CyclicBarrier的基本操作组合就是await。当所有的伙伴(parities)都调用了await,才会继续执行任务,并自动进行重置。注意: 正常情况下CyclicBarrier的重置都是自动发生的,如果我们调用了reset方法,但还有线程在等待,就会导致线程被打扰,抛出BrokenBarrierException。CyclicBarrier侧重点是 线程 而不是事件。它的典型应用场景是用来等待并发线程结束。
CountDownLatch 代码示例
用CountDownLatch实现等出租车排队情况。
public class LatchSample {
public static void main(String[] args) throws InterruptedException{
CountDownLatch latch = new CountDownLatch(6);
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new FirstBatchWorker(latch));
t.start();
}
for (int i = 0; i < 5; i++){
Thread t = new Thread(new SecondBatchWorker(latch));
t.start();
}
//注意:这里只是为了以演示为目的,工程中不推荐此协调方法
while (latch.getCount() != 1)
Thread.sleep(100L);
System.out.println("Wait for first Batch finish!");
latch.countDown();
}
}
class FirstBatchWorker implements Runnable {
private CountDownLatch latch;
public FirstBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("First Batch executed!");
latch.countDown();
}
}
class SecondBatchWorker implements Runnable {
private CountDownLatch latch;
public SecondBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
System.out.println("Second Batch executed!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
在实际应用中的条件依赖,往往没有这么别扭,CountDownLatch用于在线程间等待操作结束,是非常简单普遍的用法。通过CountDown 和 await组合通信是非常高效的,工程中不建议用上面循环的那种等待方式。
CyclicBarrier 代码示例
CyclicBarrier 反应的是线程并行运行时的协调,在下面的示例里,从逻辑上5个工作线程更像是代表了5个可以就绪的空车,而不再是5个乘客。
public class CyclicBarrierSample {
public static void main(String[] args) throws InterruptedException{
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("Action ... Go!");
}
});
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new CyclicWorker(barrier));
t.start();
}
}
static class CyclicWorker implements Runnable {
private CyclicBarrier barrier;
public CyclicWorker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
System.out.println("Executed!(执行)");
barrier.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
运行结果:
注意:
为了让输出更能表达运行时序,我们使用了CyclicBarrier特有的barrierAction,当屏障被触发时,Java自动调度该动作。因为CyclicBarrier 会自动进行重置,所以这个逻辑可以非常自然的支持更多排队人数.