Semaphore的用法能够实现CountDownLatch和CyclicBarrier的功能,与这两者不同的是,Semaphore的计数器是累加的,当它的值等于acquire(n);要求的n时线程才能被唤醒。
Semaphore实现CountDownLatch的功能
public class SemaphoreTest {
// 初始化信号量计数器为0
private static Semaphore semaphore=new Semaphore(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程A:"+Thread.currentThread()+"开始执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A:"+Thread.currentThread()+"执行任务结束");
// 调用release信号量计数器加1
semaphore.release();
}
});
// 将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程B:"+Thread.currentThread()+"开始执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B:"+Thread.currentThread()+"执行任务结束");
// 调用release信号量计数器加1
semaphore.release();
}
});
// 等到信号量计数器=2,取消阻塞
semaphore.acquire(2);
System.out.println("所有线程都执行完毕");
executorService.shutdown();
}
}
执行结果:
线程A:Thread[pool-1-thread-1,5,main]开始执行任务
线程B:Thread[pool-1-thread-2,5,main]开始执行任务
线程B:Thread[pool-1-thread-2,5,main]执行任务结束
线程A:Thread[pool-1-thread-1,5,main]执行任务结束
所有线程都执行完毕
结果分析:
开始Semaphore初始化计数器为0,主线程创建完两个线程后,调用semaphore.acquire(2);会阻塞。当线程A执行完调用semaphore.release();后,计数器的值会加1,同理线程B执行完后,计数器的值等于2。这时候已经满足semaphore.acquire(2);的要求,主线程继续往下执行。
Semaphore实现CyclicBarrier的功能
public class SemaphoreTest2 {
private static Semaphore semaphore=new Semaphore(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程A:"+Thread.currentThread()+"开始执行任务第1步");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A:"+Thread.currentThread()+"执行任务第1步结束");
semaphore.release();
}
});
// 将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程B:"+Thread.currentThread()+"开始执行任务第1步");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B:"+Thread.currentThread()+"执行任务第1步结束");
semaphore.release();
}
});
semaphore.acquire(2);
// 将线程A1添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程A:"+Thread.currentThread()+"开始执行任务第2步");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A:"+Thread.currentThread()+"执行任务第2步结束");
semaphore.release();
}
});
// 将线程B1添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程B:"+Thread.currentThread()+"开始执行任务第2步");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B:"+Thread.currentThread()+"执行任务第2步结束");
semaphore.release();
}
});
semaphore.acquire(2);
System.out.println("所有线程都执行完毕");
executorService.shutdown();
}
}
执行结果:
线程A:Thread[pool-1-thread-1,5,main]开始执行任务第1步
线程B:Thread[pool-1-thread-2,5,main]开始执行任务第1步
线程A:Thread[pool-1-thread-1,5,main]执行任务第1步结束
线程B:Thread[pool-1-thread-2,5,main]执行任务第1步结束
线程A:Thread[pool-1-thread-1,5,main]开始执行任务第2步
线程B:Thread[pool-1-thread-2,5,main]开始执行任务第2步
线程A:Thread[pool-1-thread-1,5,main]执行任务第2步结束
线程B:Thread[pool-1-thread-2,5,main]执行任务第2步结束
所有线程都执行完毕
结果分析:
这里模拟了CyclicBarrier计数器复用的特性,主线程创建完线程A和线程B后阻塞,线程A和线程B执行完任务分别调用release使得主线程被唤醒,主线程继续创建线程A1和线程B1又阻塞,线程A1和线程B1执行完任务分别调用release使得主线程被唤醒,主线程继续执行后续逻辑。
CountDownLatch、CyclicBarrier、Semaphore三者总结
CountDownLatch
CountDownLatch countDownLatch=new CountDownLatch(n);
通过调用countDownLatch.await();阻塞,每调用countDownLatch.countDown();1次,计数器n会减1,当n=0时,所有阻塞的线程被唤醒。
CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(n);
通过调用cyclicBarrier.await();阻塞,同时计数器n会减1,当n=0时,所有阻塞的线程被唤醒。n能够实现复用,也就是n被重置为原来的值,唤醒后的线程可以继续await阻塞,然后等待n=0,继续被唤醒。
Semaphore
Semaphore semaphore=new Semaphore(n);
与前两者对比,这里的计数器是递增的。通过调用semaphore.acquire(m);阻塞,如果m=n则被唤醒。线程通过调用semaphore.release();来使得计数器n加1,一旦使得n=m,那么所有阻塞的线程被唤醒。