1. CountDownLatch
CountDownLatch其实就是一个计数器,应用场景主要是在线程之间的等待上面。
举个简单的例子:假设学校规定必须全部的学生到齐,老师才能上课。老师首先来到教室,这时班上的同学都还没有来,老师设置计数器为50,进行逐一倒数,并执行等待方法await()。然后同学们相继到达教室,计数器的值也就一直在递减。最后,全班同学都到达了,那么计数器也就减为0了,这时老师就无需等待了,可以继续执行任务。
CountDownLatch的方法并不多,只有如下几个方法:
方法 | 功能 |
---|---|
CountDownLatch(int count) | 构造方法,设置计数器的初始值 |
await() | 等待方法,等待计数器减为0 |
await(long timeout, TimeUnit unit) | 限时等待,如果超过一定时间就不再等待 |
countDown() | 计数器数目-1 |
getCount() | 获得计数器的数值 |
下面举出一个具体的实例: |
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(10);
Thread thread = new Thread(){
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("学生" + (i+1) + "到达教室");
countDownLatch.countDown();
}
}
};
System.out.println("老师到达教室");
thread.start();
try {
countDownLatch.await();
System.out.println("学生到齐,开始上课");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
老师到达教室
学生1到达教室
学生2到达教室
学生3到达教室
学生4到达教室
学生5到达教室
学生6到达教室
学生7到达教室
学生8到达教室
学生9到达教室
学生10到达教室
学生到齐,开始上课
2. CyclicBarrier
CyclicBarrier可以理解为一个循环栅栏,其实和CountDownLatch有一定的相同点。就是都是需要进行计数,CyclicBarrier是等待所有人都准备就绪了,才会进行下一步。不同点在于,CyclicBarrier结束了一次计数之后会自动开始下一次计数。而CountDownLatch只能完成一次。
下面来举个简单的例子: 例如一个军队上战场打仗,出征前需要集合报数。假设总共有10个士兵,依次报数,当10个士兵都报完数了,才能出征。
下面是CyclicBarrier中的一些主要方法:
方法 | 功能 |
---|---|
await() | 等待方法 |
await(long timeout, TimeUnit unit) | 限时等待方法 |
isBroken() | 查看等待的线程是否被中断 |
reset() | 重置栅栏,如果有线程在等待会抛出BrokenBarrierException |
getNumberWaiting() | 查看当前有多少个线程在等待 |
下面是一个简单的例子进行运用:
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
public static void main(String[] args) {
// 这里利用了CyclicBarrier的另外一个构造函数传入Runnable接口
final CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
@Override
public void run() {
// 当等待成功后会执行
System.out.println("教官:全员出发");
}
});
ExecutorService executorService = Executors.newFixedThreadPool(10);
System.out.println("教官:开始报数");
Thread thread = new Thread() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "士兵就位");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "号士兵出发");
} catch (Exception e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
executorService.execute(thread);
}
}
}
运行结果:
教官:开始报数
pool-1-thread-2士兵就位
pool-1-thread-3士兵就位
pool-1-thread-1士兵就位
pool-1-thread-5士兵就位
pool-1-thread-4士兵就位
pool-1-thread-6士兵就位
pool-1-thread-8士兵就位
pool-1-thread-7士兵就位
pool-1-thread-9士兵就位
pool-1-thread-10士兵就位
教官:全员出发
pool-1-thread-10号士兵出发
pool-1-thread-2号士兵出发
pool-1-thread-1号士兵出发
pool-1-thread-3号士兵出发
pool-1-thread-9号士兵出发
pool-1-thread-7号士兵出发
pool-1-thread-8号士兵出发
pool-1-thread-6号士兵出发
pool-1-thread-4号士兵出发
pool-1-thread-5号士兵出发
从上面的代码中可以看到,只有全员到齐了,才会出发。
接下来,我们对比下CountDownLatch和CyclicBarrier的区别:
- CountDownLatch通常用于一个线程等多个线程。CyclicBarrier则是多个线程互相等待,然后一起执行。
- CountDownLatch的countDown方法执行后,不会阻塞,会继续执行。CyclicBarrier的await方法执行后,会被阻塞。
- CountDownLatch不能复用,CyclicBarrier能够复用。
3. Semaphore
Semaphore主要是用来控制资源数量的工具,可以理解为信号量。初始化时给定信号量的个数,其他线程可以来尝试获得许可,当完成任务之后需要释放响应的许可。
同样我们来举个例子: 我们去银行办理业务通常都需要填表,而银行通常会放置几支笔。当时办理业务的人数肯定是多于笔的总数的。我们就可以这样理解,这几只笔就相当于信号量,排在前面的人可以先获得许可,用完笔之后,就需要把笔还回去,相当于释放许可。
方法 | 功能 |
---|---|
Semaphore(int permits) | 构造方法,给定许可证的个数 |
acquire() | 尝试获得一个许可 |
acquire(int permits) | 尝试获得多个许可 |
release() | 尝试释放许可 |
release(int permits) | 尝试释放多个许可 |
下面展示一个简单的例子:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class TestSemaphore {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(10);
ExecutorService executorService = Executors.newFixedThreadPool(5);
Thread thread = new Thread() {
@Override
public void run() {
try {
semaphore.acquire(1);
System.out.println(Thread.currentThread().getName() + "拿到笔" + System.currentTimeMillis());
Thread.sleep(1000);
semaphore.release(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
for (int i = 0; i < 10; i++) {
executorService.execute((thread));
}
}
}
输出结果:
pool-1-thread-1拿到笔1670901372706
pool-1-thread-5拿到笔1670901372706
pool-1-thread-2拿到笔1670901372706
pool-1-thread-4拿到笔1670901372706
pool-1-thread-3拿到笔1670901372706
pool-1-thread-1拿到笔1670901373712
pool-1-thread-4拿到笔1670901373712
pool-1-thread-5拿到笔1670901373712
pool-1-thread-2拿到笔1670901373712
pool-1-thread-3拿到笔1670901373712
从结果中可以看到,在同一时刻只有5个线程能拿到笔。
4.Exchanger
Exchanger是一个用于线程间协作的工具类。它提供了一个交换的同步点,在这个交换点两个线程能够交换数据。具体交换数据的方式是通过exchange函数实现的,如果一个线程先执行exchange函数,那么它会等待另一个线程也执行exchange方法。当两个线程都到达了同步交换点,两个线程就可以交换数据。
同样我们来举个例子: 在战争中,长官会派士兵去前方侦查,长官则会留在司令部等待消息。所以长官会先执行exchange方法进行等待,当士兵侦查到消息,返回到司令部了,也会执行exchage方法。这样,长官和士兵就可以交换消息了。
方法 | 功能 |
---|---|
exchange(V x) | 将数据交换给另一个线程,同时返回获取的数据 |
exchange(V x, long timeout, Timeout unit) | 和上一个方法一样,只是增加了超时等待功能 |
下面展示一个简单的例子:
import java.util.concurrent.Exchanger;
public class TestExchanger {
public static void main(String[] args) {
final Exchanger<String> exchanger = new Exchanger<String>();
Thread chief = new Thread() {
@Override
public void run() {
try {
String message = exchanger.exchange("长官:收到:就地隐蔽!");
System.out.println(message);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
Thread soldier = new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
String message = exchanger.exchange("士兵:报告长官:前方有敌情!");
System.out.println(message);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
soldier.start();
chief.start();
}
}
输出结果:
士兵:报告长官:前方有敌情!
长官:收到:就地隐蔽!
参考文章:
大白话说java并发工具类-CountDownLatch,CyclicBarrier
大白话说java并发工具类-Semaphore,Exchanger