【面试专栏】Java常用并发工具类

1. 简介

  Java中常见的四种并发工具类:

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Exchanger

2. 主线程等待子线程之CountDownLatch

  • 原理
      CountDownLatch允许一个或多个线程等待其他一组线程完成操作,再继续执行。
      CountDownLatch的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。当调用CountDownLatch的countDown方法时N减1,CountDownLatch的await方法会阻塞当前线程(即主线程在闭锁上等待),直到N减为零,此时闭锁的主线程继续执行任务。
  • 示例图
  • 使用场景
      超市的某一种商品,当促销时往往会有多人从货架上取出商品,只到该商品销售完后,促销结束。
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

	public static void main(String[] args) throws InterruptedException {
		CountDownLatch countDownLatch = new CountDownLatch(5);
		System.out.println("商品货源充足,促销开始!");
		for (int i = 1; i <= 5; i++) {
			new Thread(() -> {
				System.out.println("商品编号[" + Thread.currentThread().getName() + "]已售出");
				countDownLatch.countDown();
			}, String.valueOf(i)).start();
		}

		countDownLatch.await();
		System.out.println("商品已售罄,促销结束!");
	}

}
商品货源充足,促销开始!
商品编号[2]已售出
商品编号[3]已售出
商品编号[1]已售出
商品编号[4]已售出
商品编号[5]已售出
商品已售罄,促销结束!

3. 同步到达屏障之CyclicBarrier

  • 原理
      让一组线程在到达一处屏障(即同步点)时被阻塞,只到最后一个线程达到屏障点,所有被阻塞的线程被释放继续执行。
      CyclicBarrier的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。每个线程调用CyclicBarrier的await方法使自己被阻塞,当N个线程调用了await方法后,所有线程停止等待,继续执行。
  • 与CountDownLatch对比
      相同点:都需要等待线程然后才能继续执行。
      不相同点:可以循环使用。假如,将CyclicBarrier的计数器设置为10,当等待到10个线程后,计数器归0,然后可以继续等待10个线程。
  • 示例图
  • 使用场景
      班主任收集学生信息,当全班同学的信息收集完成后,才可以交给教务处。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

	public static void main(String[] args) {
		CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
			System.out.println("收集信息完成,转交教务处!");
		});

		for (int i = 1; i <= 5; i++) {
			new Thread(() -> {
				System.out.println("收集到学生[" + Thread.currentThread().getName() + "]的信息");
				try {
					cyclicBarrier.await();
				} catch (InterruptedException | BrokenBarrierException e) {
					e.printStackTrace();
				}
			}, String.valueOf(i)).start();
		}

	}

}
收集到学生[1]的信息
收集到学生[5]的信息
收集到学生[3]的信息
收集到学生[2]的信息
收集到学生[4]的信息
收集信息完成,转交教务处!

4. 控制并发线程数之Semaphore

  • 原理
      JDK1.5就提供了Semaphore来统计一定数量的线程获得许可而对公共资源的访问。
      Semaphore的构造函数入一个int类型的参数N作为可颁发的许可数量,线程通过调用acquire方法获取许可,只有获得许可的线程才可以执行任务,执行完成后调用release方法归还许可。当线程获取许可失败时,表示已经到达了最大的并发线程数,即许可已经颁发完毕,需要等待有线程归还后再次尝试获取。
  • 示例图
  • 使用场景
      餐厅只有两个位置可以就餐,每个人需要就餐时,需要先看位置是否为空。为空时可直接就餐,不为空时则等待。
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {

	public static void main(String[] args) {
		Semaphore semaphore = new Semaphore(2);

		for (int i = 1; i <= 5; i++) {
			new Thread(() -> {
				try {
					semaphore.acquire();
					TimeUnit.SECONDS.sleep(3);

					System.out.println("顾客[" + Thread.currentThread().getName() + "]就餐,时间为3秒钟");
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					semaphore.release();
				}
			}, String.valueOf(i)).start();
		}
	}

}
顾客[1]就餐,时间为3秒钟
顾客[2]就餐,时间为3秒钟
顾客[4]就餐,时间为3秒钟
顾客[3]就餐,时间为3秒钟
顾客[5]就餐,时间为3秒钟

5. 交换数据之Exchanger

  • 原理
      Exchanger是一个线程间协作的工具类,用于线程间的数据交换。线程A调用exchange方法到达同步点,等待数据交换,线程B也调用exchange方法到达同步点,此时线程A和线程B进行数据交换。
  • 示例图
  • 使用场景
      幼儿园进行玩具分享会,当两个小朋友都原因交换玩具时,进行交换玩具。
import java.util.concurrent.Exchanger;

public class ExchangerDemo {

	public static void main(String[] args) {
		Exchanger<String> exchanger = new Exchanger<>();

		for (int i = 1; i <= 6; i++) {
			new Thread(() -> {
				final String treadName = Thread.currentThread().getName();
				try {
					String data = "[物体" + treadName + "]";
					System.out.println("小朋友[" + treadName + "]交换前拥有" + data);
					data = exchanger.exchange(data);

					System.err.println("小朋友[" + treadName + "]交换后拥有" + data);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}, String.valueOf(i)).start();
		}
	}

}
小朋友[1]交换前拥有[物体1]
小朋友[3]交换前拥有[物体3]
小朋友[6]交换前拥有[物体6]
小朋友[4]交换前拥有[物体4]
小朋友[2]交换前拥有[物体2]
小朋友[5]交换前拥有[物体5]
小朋友[3]交换后拥有[物体1]
小朋友[1]交换后拥有[物体3]
小朋友[4]交换后拥有[物体6]
小朋友[6]交换后拥有[物体4]
小朋友[5]交换后拥有[物体2]
小朋友[2]交换后拥有[物体5]
posted @ 2021-01-10 15:43  C3Stones  阅读(33)  评论(0编辑  收藏