Semaphore是一个计数信号量,常用于限制可以访问某些资源的线程数量,即一种用来控制并发量的共享锁。
CountDownLatch是一个倒计数器,起跑信号。
CyclicBarrier是一个循环栅栏,排队摩天轮。
代码示例:
下面分别是三种并发协同工具的使用测试
public class SemaphoreDemo {
static Semaphore sp = new Semaphore(6);
// static KaneSemaphore sp = new KaneSemaphore(6);
public static void main(String[] args) {
for (int i = 0; i < 12; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
sp.acquire();//获取信号量
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("当前信号量限制...");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完了");
sp.release();
}
}).start();
}
}
}
执行结果:
当前信号量限制...
当前信号量限制...
当前信号量限制...
当前信号量限制...
当前信号量限制...
当前信号量限制...
执行完了
当前信号量限制...
执行完了
执行完了
执行完了
当前信号量限制...
当前信号量限制...
当前信号量限制...
执行完了
执行完了
当前信号量限制...
当前信号量限制...
执行完了
执行完了
执行完了
执行完了
执行完了
执行完了
6个6个执行
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
// KaneCountDownLatch latch = new KaneCountDownLatch(6);
//一种方式
for (int i = 0; i < 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始准备...");
latch.countDown();//计数减一
}
}).start();
Thread.sleep(1000L);
}
latch.await();//每个线程执行一次则-1,当lache为0时开始向下执行,就是这些现场都准备就绪,然后一起去干同一件事
// //另一种方式
// for (int i = 0; i < 6; i++) {
// new Thread(new Runnable() {
// @Override
// public void run() {
// latch.countDown();
// try {
// latch.await();
// System.out.println("线程:" + Thread.currentThread().getName() + "执行完毕");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }).start();
// }
System.out.println("开始干活...");
}
}
执行结果
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始干活...
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(4);
// KaneCyclicBarrier barrier = new KaneCyclicBarrier(4);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// barrier.await();
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("任务开始执行");
}
}).start();
Thread.sleep(500L);
}
}
}
执行结果
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
任务开始执行
...
4个4个执行
并发场景测试示例
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
AccessCounter accessCounter = new AccessCounter();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始准备...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
accessCounter.access();
}
}).start();
Thread.sleep(1000L);
countDownLatch.countDown();
}
System.out.println("开始干活...");
System.out.println("accessCounter------------------" + accessCounter.getAccessCount());
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始准备...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
accessCounter.atomicAccess();
}
}).start();
Thread.sleep(1000L);
countDownLatch.countDown();
}
System.out.println("开始干活...");
System.out.println("atomicCount------------------" + accessCounter.getAtomicCount());
}
测试结果
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始干活...
accessCounter------------------3
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始准备...
开始干活...
atomicCount------------------10
CountDownLatch应用场景和CycleBarrier应用场景
- CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
- CycleBarrier是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。
CountDownLatch可以理解成倒计时锁,一个线程 等待其他线程执行 countDown(),阻塞的是单个进程,对执行 countDown 的线程几乎没有什么影响。
1)计数值为 1 的CountDownLatch可以用作简单的开/关锁或门:当每个线程都调用了CountDownLatch的await方法后,这些线程都在门处等待,直到另外一个线程调用了该CountDownLatch的countDown方法 。
2) 初始化计数值为N的CountDownLatch可用于使一个线程等待,直到N 个线程完成某个操作,或者某个操作已完成 N 次的场景。
CountDownLatch的一个特性是它不需要让调用了countDown方法的线程等待CountDownLatch的计数达到零,该线程可以继续执行;它只是通过await阻止任何线程,直到所有线程都可以通过。
场景1,高考ing,监考老师发下去试卷,等待学生答题,有的学生提前交卷了,有的学生最后时间到了才交卷,老师到时间后收卷,贴封条,下班回家。
场景2,屌丝坐长途客车去城里,早早起床刷牙,为了能赶上客车,并顺利的第2个上车,可客车不发车,一直等到客车上满人了这才发车。
CycleBarrier可以理解成是个障碍,所有线程必须到齐后才能一起通过该障碍,所有线程都被阻塞。
场景1,公司团建,其中一个项目就是全体员工在完成其他项目时,到达一个高达4米的高墙,要求所有人一个不能少的越过高墙,才能继续其他项目。
场景2,玩LOL,出现是个人不同加载状态,最后一个人由于个人原因始终无法加载100%,于是等待所有人状态都加载完毕才开始。
CountDownLatch示例
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
IntStream.range(0, 3).forEach(i -> {
new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println("hi ni hao");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
});
System.out.println("任务线程启动完成");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程启动完成");
}
上述案例中启动了三个任务线程,运行可以发现,任务线程启动完成后主线程陷入等待,直到每个任务线程调用了countDownLatch的countDown方法,使得总的countDown计数到0,主线程才被唤醒继续执行。
CyclicBarrier示例
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
try {
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + " hello " + finalI);
long t = (long) (Math.random() * 2000);//模拟执行耗时
System.out.println("t=" + t);
Thread.sleep((t));
cyclicBarrier.await();//在屏障前等待,直到所有线程到达屏障处
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + " world " + finalI);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
线程调用了CyclicBarrier的await方法后会陷入等待,直到所有各方都在此CyclicBarrier上调用了await方法 。如果当前线程不是最后一个到达屏障处的线程,那么它会出于线程调度目的而被禁用并处于休眠状态。