Java中的并发工具类详细解析

 更新时间:2023年12月27日 11:22:17   作者:Java都不学  
这篇文章主要介绍了Java中的并发工具类详细解析,CountDownLatch、 CyclicBarrier 和 Semaphore 工具类提供了一种并发流程控制的手段,Exchanger 工具类则提供了在线程间交换数据的一种手段,需要的朋友可以参考下

前言

CountDownLatch、 CyclicBarrier 和 Semaphore 工具类提供了一种并发流程控制的手段

Exchanger 工具类则提供了在线程间交换数据的一种手段。

等待多线程完成的 CountDownLatch

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

public class CountDownLatchTest {
    staticCountDownLatch c = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
                // N-1;N为0时,退出await方法
                c.countDown();
                System.out.println(2);
                c.countDown();
            }
        }).start();
        // 带指定时间的 await 方法——await(long time,TimeUnit unit)
        c.await();
        System.out.println("3");
    }
}

CountDownLatch 的构造函数接收一个 int 类型的参数作为计数器,如果你想等待 N 个点完成,这里就传入 N。

当我们调用 CountDownLatch 的 countDown 方法时,N 就会减 1,CountDownLatch 的 await 方法会阻塞当前线程,直到 N 变成零。

由于 countDown 方法可以用在任何地方,所以这里说的 N 个点,可以是 N 个线程,也可以是 1 个线程里 的 N 个执行步骤。

用在多个线程时,只需要把这个 CountDownLatch 的引用传递到线程 里即可。

计数器必须大于等于 0,只是等于 0 时候,计数器就是零,调用 await 方法时不会阻塞当前线程。

CountDownLatch 不可能重新初始化或者修改 CountDownLatch 对象的内部计数器的值。

一个线程调用 countDown 方法 happen-before,另外一个线程调用 await 方法。

同步屏障 CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。

它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

CyclicBarrier 简介

CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

public class CyclicBarrierTest {
    static CyclicBarrier c = new CyclicBarrier(2);
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 到达屏障
                    c.await();
                } catch (Exception e) {
                }
                System.out.println(1);
            }
        }).start();
        try {
            // 到达屏障
            c.await();
        } catch (Exception e) {
        }
        System.out.println(2); // 1 2 或 2 1 到达顺序不唯一
    }
}

CyclicBarrier 还提供一个更高级的构造函数 CyclicBarrier(int parties,Runnable barrier-Action),用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景。

import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest2 {
    static CyclicBarrier c = new CyclicBarrier(2, new A());
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    c.await();
                } catch (Exception e) {
                }
                System.out.println(1);
            }
        }).start();
        try {
            c.await();
        } catch (Exception e) {
        }
        System.out.println(2); // 3 1 2
/*因为 CyclicBarrier 设置了拦截线程的数量是 2,
所以必须等代码中的第一个线程和线程 A 都执行完之后,
才会继续执行主线程,然后输出 2*/
    }
    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println(3);
        }
    }
}

CyclicBarrier 的应用场景

CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景

/**
 * 银行流水处理服务类
 *
 * @authorftf
 */
public class BankWaterService implements Runnable {
    /**
     * 创建 4 个屏障,处理完之后执行当前类的 run 方法
     */
    private CyclicBarrier c = new CyclicBarrier(4, this);
    /**
     * 假设只有 4 个 sheet,所以只启动 4 个线程
     */
    private Executor executor = Executors.newFixedThreadPool(4);
    /**
     * 保存每个 sheet 计算出的银流结果
     */
    private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new
            ConcurrentHashMap<String, Integer>();
    private void count() {
        for (int i = 0; i < 4; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    // 计算当前 sheet 的银流数据,计算代码省略
                    sheetBankWaterCount
                            .put(Thread.currentThread().getName(), 1);
                    // 银流计算完成,插入一个屏障
                    try {
                        c.await();
                    } catch (InterruptedException |
                            BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    @Override
    public void run() {
        int result = 0;
        // 汇总每个 sheet 计算出的结果
        for (Entry<String, Integer> sheet : sheetBankWaterCount.entrySet()) {
            result += sheet.getValue();
        }
        // 将结果输出 sheetBankWaterCount.put("result", result);
        System.out.println(result);
    }
    public static void main(String[] args) {
        BankWaterService bankWaterCount = new BankWaterService();
        bankWaterCount.count();
    }
}

CyclicBarrier 和 CountDownLatch 的区别

CountDownLatch 的计数器只能使用一次,而 CyclicBarrier 的计数器可以使用 reset() 方法重置。所以 CyclicBarrier 能处理更为复杂的业务场景。

例如,如果计算发生错误, 可以重置计数器,并让线程重新执行一次。

CyclicBarrier 还提供其他有用的方法,比如 getNumberWaiting 方法可以获得 CyclicBarrier 阻塞的线程数量。

isBroken()方法用来了解阻塞的线程是否被中断。

public class CyclicBarrierTest3 {
    staticCyclicBarrier c = new CyclicBarrier(2);
    public static void main(String[] args) throws InterruptedException,
            BrokenBarrierException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    c.await();
                } catch (Exception e) {
                }
            }
        });
        thread.start();
        thread.interrupt();
        try {
            c.await();
        } catch (Exception e) {
            System.out.println(c.isBroken()); //true
        }
    }
}

控制并发线程数的 Semaphore

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

从字面上很难理解 Semaphore 所表达的含义,只能把它比作是 控制流量的红绿灯。比如××马路要限制流量,只允许同时有一百辆车在这条路上行使, 其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车 会看到红灯,不能驶入××马路,但是如果前一百辆中有 5 辆车已经离开了××马路,那么 后面就允许有 5 辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执 行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。

Semaphore 可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连 接。假如有一个需求,要读取几万个文件的数据,因为都是 IO 密集型任务,我们可以启 动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的 连接数只有 10 个,这时我们必须控制只有 10 个线程同时获取数据库连接保存数据,否 则会报错无法获取数据库连接。这个时候,就可以使用 Semaphore 来做流量控制

public class SemaphoreTest {
    private static final int THREAD_COUNT = 30;
    private static ExecutorService threadPool =
            Executors.newFixedThreadPool(THREAD_COUNT);
    private static Semaphore s = new Semaphore(10);
    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        s.acquire();
                        System.out.println("save data");
                        s.release();
                    } catch (InterruptedException e) {
                    }
                }
            });
        }
        threadPool.shutdown();
    }
}

虽然有 30 个线程在执行,但是只允许 10 个并发执行。Semaphore 的构 造方法 Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。 Semaphore(10)表示允许 10 个线程获取许可证,也就是最大并发数是 10。Semaphore 的用法也很简单,首先线程使用 Semaphore 的 acquire()方法获取一个许可证,使用完之后调用 release()方法归还许可证。还可以用 tryAcquire()方法尝试获取许可证。

Semaphore 还提供一些其他方法,具体如下。

  • intavailablePermits():返回此信号量中当前可用的许可证数。
  • intgetQueueLength():返回正在等待获取许可证的线程数。
  • booleanhasQueuedThreads():是否有线程正在等待获取许可证。
  • void reducePermits(int reduction):减少 reduction 个许可证,是个 protected 方 法。
  • Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个 protected 方法。

线程间交换数据的 Exchanger

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger 用于进行线程间 的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange 方法交换数据,如果第一个线程先执行 exchange()方法,它会一直等待第二个线程也执行 exchange 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

Exchanger 可以用于遗传算法,遗传算法里需要选出两个人作为交配对象,这时候会 交换两人的数据,并使用交叉规则得出 2 个交配结果。Exchanger 也可以用于校对工作, 比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水,为了避免错误,采 用 AB 岗两人进行录入,录入到 Excel 之后,系统需要加载这两个 Excel,并对两个 Excel 数据进行校对,看看是否录入一致

public class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<String>();
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
    public static void main(String[] args) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String A = "银行流水 A"; // A 录入银行流水数据
                    exgr.exchange(A);
                } catch (InterruptedException e) {
                }
            }
        });
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String B = "银行流水 B"; // B 录入银行流水数据
                    String A = exgr.exchange(B);
                    System.out.println("A 和 B 数据是否一致:" + A.equals(B) + ",A 录入的是:"
                            + A + ",B 录入是:" + B);
                } catch (InterruptedException e) {
                }
            }
        });
        threadPool.shutdown();
    }
}

如果两个线程有一个没有执行 exchange()方法,则会一直等待,如果担心有特殊情 况发生,避免一直等待,可以使用 exchange(V x,longtimeout,TimeUnit unit)设置最大等待时长。

到此这篇关于Java中的并发工具类详细解析的文章就介绍到这了,更多相关Java中的并发工具内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Spring Data JPA系列之投影(Projection)的用法

    详解Spring Data JPA系列之投影(Projection)的用法

    本篇文章主要介绍了详解Spring Data JPA系列之投影(Projection)的用法,具有一定的参考价值,有兴趣的可以了解一下
    2017-07-07
  • 深入理解JVM之Java对象的创建、内存布局、访问定位详解

    深入理解JVM之Java对象的创建、内存布局、访问定位详解

    这篇文章主要介绍了深入理解JVM之Java对象的创建、内存布局、访问定位,结合实例形式详细分析了Java对象的创建、内存布局、访问定位相关概念、原理、操作技巧与注意事项,需要的朋友可以参考下
    2019-09-09
  • java8 Stream API之reduce使用说明

    java8 Stream API之reduce使用说明

    这篇文章主要介绍了java8 Stream API之reduce使用说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Spring运行时手动注入bean的方法实例

    Spring运行时手动注入bean的方法实例

    spring给我们提供了IOC服务,让我们可以用注解的方式,方便的使用bean的相互引用,下面这篇文章主要给大家介绍了关于Spring运行时手动注入bean的相关资料,需要的朋友可以参考下
    2022-05-05
  • JavaSE经典小练习项目之拷贝文件夹

    JavaSE经典小练习项目之拷贝文件夹

    文件拷贝是一个常见的任务,无论是备份文件,还是将文件从一个位置复制到另一个位置,文件拷贝都是必不可少的,这篇文章主要给大家介绍了关于JavaSE经典小练习项目之拷贝文件夹的相关资料,需要的朋友可以参考下
    2023-10-10
  • Mybatis批量修改联合主键数据的两种方法

    Mybatis批量修改联合主键数据的两种方法

    最近遇上需要批量修改有联合主键的表数据,找很多资料都不是太合适,最终自己摸索总结了两种方式可以批量修改数据,对Mybatis批量修改数据相关知识感兴趣的朋友一起看看吧
    2022-04-04
  • Java排序算法总结之归并排序

    Java排序算法总结之归并排序

    这篇文章主要介绍了Java排序算法总结之归并排序,较为详细的分析了归并排序的原理与java实现技巧,需要的朋友可以参考下
    2015-05-05
  • 详解Java8新特性Stream之list转map及问题解决

    详解Java8新特性Stream之list转map及问题解决

    这篇文章主要介绍了详解Java8新特性Stream之list转map及问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Spring Boot 日志功能深度解析与实践指南

    Spring Boot 日志功能深度解析与实践指南

    本文详细介绍了SpringBoot的日志功能,包括默认日志框架Logback,日志级别配置,日志格式自定义,日志文件输出,日志归档与清理,自定义日志配置,与其他日志框架的集成以及日志性能优化,通过结合实际场景,提供了详细的配置与实践指南,感兴趣的朋友一起看看吧
    2025-01-01
  • Maven Repository仓库的具体使用

    Maven Repository仓库的具体使用

    本文主要介绍了Maven Repository仓库的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05

最新评论