1、CountDownLatch
CountDownLatch 是一个用于协调多个线程等待直到某个条件满足的同步辅助类。它允许一个或多个线程等待直到其他线程完成了一组操作,然后这些等待的线程才会继续执行。CountDownLatch 的核心属性是基于 AQS 的共享锁机制实现的,它有一个核心的计数器,用于记录等待线程的数量。当一个线程完成了它的任务后,计数器的值会减1,直到计数器值为0,这时所有等待的线程都会被唤醒,继续执行任务。
package com.example.juc;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* 查询不同机场中【xx -> yy】的飞机票数
*/
public class TicketTest {
private static final String[] airports = {"浦东机场", "昌北机场", "海南机场"};
private static final CountDownLatch countDownLatch = new CountDownLatch(airports.length);
private static final Integer[] tickets = new Integer[airports.length];
public static void main(String[] args) {
for (int i = 0; i < airports.length; i++) {
System.out.println("正在查询" + airports[i] + "中从 xx -> yy 的飞机票数...");
int finalI = i;
new Thread(() -> {
tickets[finalI] = new Random().nextInt(10);
System.out.println("---> " + tickets[finalI]);
try {
Thread.sleep(tickets[finalI] * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < airports.length; i++) {
System.out.println(airports[i] + "剩余票数: " + tickets[i] + "张");
}
}
}
/*
等所有的查询结果都出来后再一起打印输出
注意:
countDownLatch.countDown() 在线程内使用 newThread(()->{countDownLatch.countDown()})
countDownLatch.await(); 在线程外、循环外使用
countDown(): -1
*/
2、CyclicBarrier
CyclicBarrier 也是一个同步辅助类,它允许一组线程相互等待,直到所有的线程都达到一个公共的屏障点。CyclicBarrier 的特点是它可以重用,而 CountDownLatch 的计数器一旦为0就不能再使用。CyclicBarrier 在程序中有固定的线程数量,这些线程有时候必须等待彼此,这种情况下使用 CyclicBarrier 很有帮助。CyclicBarrier 的放行条件是等于线程数,而 CountDownLatch 的放行条件是大于等于线程数。
package com.example.juc;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 赛跑示例
*/
public class CyclicBarrierTest {
private static final Integer number = 8;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(number);
for (int i = 0; i < number; i++) {
int finalI = i;
new Thread(() -> {
try {
System.out.println("选手[" + finalI + "]正在准备中...");
int seconds = new Random().nextInt(10);
Thread.sleep(seconds * 1000);
System.out.println(seconds + "秒后,选手[" + finalI + "]已经准备就绪...");
cyclicBarrier.await();
System.out.println("选手[" + finalI + "]开始冲刺!!!");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
/*
等所有选手都准备好后再开始比赛
cyclicBarrier.await(): +1
*/
CountDownLatch 和 CyclicBarrier 的区别:
(1)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
(2)CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。 某线程中断CyclicBarrier会抛出异常,避免了所有线程无限等待。
CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减;而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
3、Semaphore
Semaphore 通常我们叫它信号量,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。常用于限流。
package com.example.juc;
import java.util.Random;
import java.util.concurrent.Semaphore;
/**
* 停车场示例
*/
public class SemaphoreTest {
private static final Integer number = 3;
private static final Semaphore semaphore = new Semaphore(number);
public static void main(String[] args) {
System.out.println("五辆车来到停车场门口!!!");
for (int i = 0; i < 5; i++) {
int finalI = i + 1;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("汽车[" + finalI + "] ---> 进停车场");
int times = new Random().nextInt(10);
Thread.sleep(times * 1000);
System.out.println(times + "秒后,汽车[" + finalI + "] <--- 出停车场");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
/**
每个线程都会先执行 semaphore.acquire(); 方法尝试获取锁,当有一个线程释放资源时,其他线程会竞争该资源
核心:
semaphore.acquire();
semaphore.release();
*/