Java并发工具类的使用

在JDK中的java.util.concurrent包里提供了几个实用的并发工具类,下面我们一起来了解下。

CountDownLatch

功能: CountDownLatch允许一个或多个线程等待其他线程完成操作

我们来看一个查询航班的例子,用户访问APP,查询北京到上海的航班信息,后台需要同时调用三家航空公司提供的API查询数据,最后将最终查询结果汇总后返回。
查询航班信息
示例代码如下:

public class FlightQueryDemo {
    private static List<String> company = Arrays.asList("中国国际航空", "东方航空", "海南航空");
    private static List<String> flightList = new ArrayList<>();
    
    public static void main(String[] args) {
        String origin = "BJ";
        String dest = "SH";
        Thread[] th = new Thread[company.size()];
        CountDownLatch cdl = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            String name = company.get(i);
            th[i] = new Thread(() -> {
                System.out.printf("%s 查询从%s到%s的机票信息\n", name, origin, dest);
                // 随机产生票数
                int val = new Random().nextInt(10);
                try {
                    TimeUnit.SECONDS.sleep(val);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                flightList.add(name + "----" + val);
                System.out.printf("%s公司查询成功!\n", name);
                cdl.countDown();
            });
            th[i].start();
        }
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("==============查询结果如下:================");
        flightList.forEach(System.out::println);
    }
}

CountDownLatch的构造函数接收一个int类型的参数作为计数器,调用CountDownLatch的countDown()方法时,计数器就会减一,CountDownLatch的await()方法会阻塞当前线程,直到计数器变为零。

注意:计数器必须大于等于0,另外CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。

CyclicBarrier

功能: 让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

我们来看一个运动员赛跑的例子,所有运动员准备好后一起开始起跑。
示例代码如下:

public class RaceDemo {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(8);
        Thread[] sportsman = new Thread[8];
        for (int i = 0; i < 8; i++) {
            sportsman[i] = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                    System.out.println(Thread.currentThread().getName() + "准备好了");
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("选手" + Thread.currentThread().getName() + "起跑");
            }, "paly[" + i + "]");
            sportsman[i].start();
        }
    }
}

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier还有一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,这里不再演示。

CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次,二CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果执行过程中发生问题,可以重置计数器,并让线程重新执行一次。

Semaphore

功能: Semaphore是用来控制同时访问特定资源的线程数量,它通过协调各个线程,比保证合理的使用公共资源。

我们来看一个停车场停车的例子,停车场停满后,其它车就需要在停车场外等待,直到有车从停车场离开,空出位置,外面的车子才可以进来。
示例代码如下:

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

        Thread[] car = new Thread[10];
        for (int i = 0; i < 10; i++) {
            car[i] = new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "可以进停车场");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                   // System.out.println(Thread.currentThread().getName() + "停车时间:" + new Random().nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                semaphore.release();
                System.out.println(Thread.currentThread().getName() + "离开停车场");
            }, "car[" + i + "]");
            car[i].start();
        }
    }
}

Exchanger

功能: Exchanger是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

我们来看一个校对工作的例子,例如:银行将纸质的资金流水通过人工的方式录入成电子银行流水,为了避免错误,采用AB两人同时进行录入,录入完成后需要对这两人录入的结果进行比较,看看是否录入一致。
示例代码如下:

public class ExchangerDemo {
    private static final Exchanger<String> exgr = new Exchanger<String>();
    private static ExecutorService pool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        pool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String A = "统计结果A";
                    exgr.exchange(A);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String B = "统计结果B";
                    String A = exgr.exchange(B);
                    System.out.println("A和B的数据是否一致:" + A.equals(B) + ", A录入的是:" + A + ", B录入的是:" + B);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

如果两个线程有一个没有执行exchanger()方法,则会一直等待。为了避免一直等待,可以使用exchange(V x, long timeout, TimeUnit unit)设置最大等待时长。

总结

本文一共介绍了四个常用的并发工具类,每个都有其特定的使用场景,后面如果遇到类似的场景问题不妨试一试这些工具类。

参考:
Java并发编程的艺术 方腾飞 魏鹏 程晓明 著


------------本文结束感谢您的阅读------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值