多线程之Exchanger

云中志

共 6470字,需浏览 13分钟

 ·

2021-07-17 17:54

多线程之Exchanger

前言

今天我们来分享最后多线程最后一个工具类组件,之后我们会继续探索多线程的相关知识:线程池、并发容器和框架,然后就是总结和查漏补缺。

今天的内容很简单,内容也不太多,但是应用场景很典型,可以解决我们实际开发中数据对比的应用需求,好了,我们直接开始吧。

Exchanger

exchanger也是jdk1.5引入的,主要用来解决线程之间数据交换的问题,和它的字面意思一样,exchanger主要是用来交换数据的,需要注意的是,交换数据的时候只能是两个(一对)线程两两交换,下面我们直接看示例代码:

public class Example {
    private final static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        executorService.execute(() -> {
            String taskStr = "10只羊";
            try {
                System.out.println("我是task1,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task1获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.execute(() -> {
            String taskStr = "一头牛";
            try {
                System.out.println("我是task2,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task2获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.execute(() -> {
            String taskStr = "50只鸡";
            try {
                System.out.println("我是task3,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task3获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() -> {
            String taskStr = "40只鸭";
            try {
                System.out.println("我是task4,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task4获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.shutdown();
    }
}

在上面的代码中,我们定义了一个交换器Exchanger,它本身是支持泛型的,这里我们定义的是string,然后定义了一个线程池,通过线程池分别启动四个线程,在四个线程中,都有这样一行代码:

String exchange = exchanger.exchange(taskStr);

它的作用就是和其他线程交换数据,并拿到交换后的数据,然后我们运行示例代码:

数据交换前,他们分别拥有10只羊,一头牛,50只鸡,40只鸭,交换后task2拿到task1的牛,task1拿到task2的羊,其他的也一样,之所以用这个例子,是因为它的交换过程确实很像原始社会的以物易物。

这里需要注意的是,交换数据的线程数量必须为双数,否则线程会一直被exchange方法阻塞,这里我们把最后一个线程先删除掉,然后运行:

因为没有线程再与task3进行数据交换,所以线程被阻塞了。

当然有时候,阻塞并非是人为的,而是在某些特殊情况下发生,为了避免因为这种情况导致线程持续阻塞,我们可以用exchanger的另一个方法:

这个方法支持设定超时时间,如果到设定时间依然没有数据交换,该方法会抛出TimeoutException异常:

关于exchanger的应用场景,我能想到的就两个,一个就是数据校验,两个线程同时操作同一批数据,我们可以对数据最终的一致性做校验,互相验证,比如两个excel的数据比对;另外一个场景和这个很类似,就是我们在实际开发经常会遇到方法A的运行条件需要根据B的运行结果进行优化调整,这时候我们就可以通过exchanger来来进行数据交换,然后再继续触发相关操作。

总结

exchanger最大的优点是她可以在运行的过程中交换数据,其他的应用场景在遇到具体问题的时候再进一步分析吧。好了,exchanger的相关内容就到这里。

今天还要补充一个小知识,是关于mysql的,是一个小知识点,也是线上发现的一个小问题,具体来说就是mysql的求和语句,如果求和字段的值全部为null,那么最终的求和结果是null,而不是0,这样就会有潜在的空指针异常:

select course_type, sum(order_id) from course GROUP BY course_type

因为order_id都是null,所以最终sum(order_id)就是null:

这样如果你用包装类接收sum(order_id)就是null,后续在操作它的时候一定要做空指针校验,否则就是个线上bug。好了,就这么多,over

- END -


浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报