public static class CountDownLatchTest {
static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SleepUtils.second(2);
System.out.println(“1”);
countDownLatch.countDown();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SleepUtils.second(4);
System.out.println(“2”);
countDownLatch.countDown();
}
}).start();
try {
// 主线程开始等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“3”);
}
}
输出如下:
1
2
3
Process finished with exit code 0
注意点
1、如果传入的参数大于2,那么主线程将会一直等待。
2、计数器必须大于0,如果为0,调用await方法不会阻塞当前线程。
应用场景
当遇到一个比较耗时的计算量较大的任务时,我们则可以考虑使用多线程来操作,将一个大任务拆分成多个小任务(一个任务相当于一个线程),当每个小任务执行完毕返回结果后,再由某一主线程对结果进行统计。
2)CyclicBarrier
CyclicBarrier即同步屏障,它主要功能是让一组线程达到一个屏障(也可以称为同步点)是被阻塞,直到最后一个线程达到屏障是,屏障才被打开,所有被拦截的线程才会继续执行。
其构造函数默认也是接收一个int类型的参数N作为屏障拦截的线程数量,每个线程调用await方法表示到达了屏障点,然后被阻塞。具体示例如下:
public class CyclicBarrierTest {
// 参数表示屏障拦截的线程数量, 每个线程调用 await方法,告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
// 屏障拦截的线程数量必须和当前的线程数一致,并且都调用await方法,否则当前所有的线程都处于等待状态
static CyclicBarrier c = new CyclicBarrier(3);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("1—1 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1—2 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2—1 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
try {
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2—2 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
}
}).start();
SleepUtils.second(2);
System.out.println("3—1 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
try {
c.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3—2 " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
}
}
输出如下:
1—1 17:05:01
2—1 17:05:01
3—1 17:05:03
3—2 17:05:03
1—2 17:05:03
2—2 17:05:03
Process finished with exit code 0
注意点
1、构造函数中的N必须为线程的总数,当最后一个线程调用await方法(到达屏障)时,屏障才会打开,被阻塞的线程才会执行,这里的N表示的含义和CountDownLatch传入的N是不一样的。
2、我们发现,当所有线程都到达屏障时,当屏障打开,接下来会优先执行哪个线程呢?如上代码答案是不确定的。但是CyclicBarrier为我们提供了一个更高级的用法,即构造函数还支持传递一个Runnable对象,当屏障打开时,优先执行Runnable中的run方法。(这一功能十分强大,完全可以替代CountDownLatch了)
应用场景
同CountDownLatch
与CountDownLatch区别:
CountDownLatch计数器只能使用一次,而CyclicBarrier的计数器可以使用 reset方法重置,所以适合更复杂的业务场景。
3)Semaphore
Semaphore即信号量,主要用来控制并发访问特定资源的线程数量,协调各个线程合理使用公共资源。
构造函数同样也是接收一个int类型的参数N作为入参,用来限制访问某一公共资源最大的线程并发数,通过acquire来获取许可证,release释放许可证。
具体示例如下:
public class SemaphoreTest {
private static final int THREAD_COUNT = 6;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new MyRunnable(i + 1));
}
threadPool.shutdown();
}
static class MyRunnable implements Runnable {
private int sleep;
public MyRunnable(int sleep) {
this.sleep = sleep;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("save data " + new SimpleDateFormat(“HH:mm:ss”).format(new Date()));
SleepUtils.second(sleep);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出如下:
save data 19:44:37
save data 19:44:37
save data 19:44:38
save data 19:44:39
save data 19:44:41
save data 19:44:43
Process finished with exit code 0
注意点
1、通过输出可以发现,线程的并发数量为2,当有一个线程执行完后则下一个线程才获取到资源。
应用场景
我们有大量的线程在完成一个巨量任务的时候,但是某一公共资源却有限定了线程的链接树,这时候就需要对这些大量线程访问这一公共资源做控制。例如当我们有上百个线程需要处理本地上G的数据文件,每个线程处理完成之后需要把结果写到数据库,而数据库只支持十个线程的并发链接,此时,对数据库的链接我们就可以通过Semaphore来控制最大连接数。
4)Exchanger
Exchanger(交换者),它是用于线程间的协作工具类,主要用于线程间数据的交换。它提供了一个同步点,在这个同步点,两个线程可以交换彼此的数据。下面看具体demo:
public class ExchangerTest {
private static final Exchanger exchanger = new Exchanger<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
String a = “aaaaaaaaaa”;
try {
String b = exchanger.exchange(a);
System.out.println(“—” + b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadPool.execute(new Runnable() {