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 @   Chen洋  阅读(622)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示