AQS系列
1、AQS核心原理
2、ReentrantLock 示例及原理
3、CountDownLatch / Semaphore 示例及使用场景
4、BlockingQueue 示例及使用场景
一、基本原理
在 《AQS核心原理》 篇中提到的,AQS 内部有一个 state 标记状态,CountDownLatch 和 Semaphore 都是借助这个状态标记作为一个计数器来实现控制多个线程的,计数器的初始值为线程的数量,每个线程执行完后计数器会减一,当计数器的值是 0 的时候,表示所有的线程都已经完成了任务,我们可以看看构成方法如下:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch 主要 API 是 CountDownLatch.countDown(),CountDownLatch.await() 。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore 主要 API 是 Semaphore.acquire() throws InterruptedException(阻塞并获取许可),Semaphore.release()(释放许可),Semaphore.tryAcquire() (尝试阻塞并获取许可)。
二、4人短跑赛 (CountDownLatch)
用一个运动会的短跑比赛作为场景,用 CountDownLatch 来实现,主要由四个运动员赛跑,等裁判一声枪响后,四个人都开始全力跑,不过比赛完成是需要四个人都跑完才算,不能是跑完一个人就算比赛结束(需要每个线程执行结束主线程才能执行)。
//实例化 CountDownLatch, 线程数量为 4
static final CountDownLatch latch = new CountDownLatch(4);
public static void main(String[] args) {
System.out.println("开始准备赛跑,所有参赛者准备");
new Thread(() -> {
try {
Thread.sleep(2000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个人跑完了,一片欢呼声~~~~~~~~~~~");
}).start();
System.out.print("第一个人准备好了|");
new Thread(() -> {
try {
Thread.sleep(4000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个人跑完了,好多人鼓掌~~~~~~~~~~~");
}).start();
System.out.print("第二个人准备好了|");
new Thread(() -> {
try {
Thread.sleep(5000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第三个人跑完了,没几个人鼓掌~~~~~~~~~~~");
}).start();
System.out.print("第三个人准备好了|");
new Thread(() -> {
try {
Thread.sleep(7000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第四个人跑完了~~~~~~~~~~~");
}).start();
System.out.println("第四个人准备好了");
System.out.println("发令枪~~~~pong...");
System.out.println("裁判员在等待着...");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("赛跑项目结束了。");
}
执行结果如下:
开始准备赛跑,所有参赛者准备
第一个人开始跑了|第二个人开始跑了|第三个人开始跑了|第四个人开始跑了
发令枪~~~~pong...
裁判员在等待着...
第一个人跑完了,一片欢呼声~~~~~~~~~~~
第二个人跑完了,好多人鼓掌~~~~~~~~~~~
第三个人跑完了,没几个人鼓掌~~~~~~~~~~~
第四个人跑完了~~~~~~~~~~~
赛跑项目结束了。
从执行结果中可以看出,四个人全部跑完 赛跑项目才结束,也就是 4 个线程都执行完之后,才回到主线程执行。
三、请求限流(Semaphore)
用一个请求限流的场景,用 Semaphore 来模拟这个场景实现。
首先实例化一个Semaphore信号量 是 3,表示每次只能有3 个线程执行任务,用一个 内部类 RequestTask 来模拟请求,每次请求耗时 2 秒。
public static void main(String[] args) {
//初始化信号量为 3,每次只有3个线程执行
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(new RequestTask(semaphore, "请求任务" + i)).start();
}
}
static class RequestTask implements Runnable{
Semaphore semaphore;
String taskName;
public RequestTask(Semaphore semaphore, String taskName) {
this.semaphore = semaphore;
this.taskName = taskName;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(taskName + "任务执行中...");
TimeUnit.SECONDS.sleep(2);
System.out.println(taskName + "任务执行完成...");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果如下:
请求任务1任务执行中...
请求任务2任务执行中...
请求任务0任务执行中...
请求任务2任务执行完成...
请求任务0任务执行完成...
请求任务1任务执行完成...
请求任务4任务执行中...
请求任务5任务执行中...
请求任务3任务执行中...
请求任务3任务执行完成...
请求任务5任务执行完成...
请求任务4任务执行完成...
请求任务7任务执行中...
请求任务6任务执行中...
请求任务8任务执行中...
请求任务6任务执行完成...
请求任务7任务执行完成...
请求任务9任务执行中...
请求任务8任务执行完成...
请求任务9任务执行完成...
从执行结果可以看出,总共 10 个线程,每次只有 3 个线程执行,最后一个时 任务9 执行,也就是说,每次是最多有 3 个线程执行。