一文理解java线程间协作问题的工具类Exchanger

简介: 在很久之前我曾写过一篇一篇文章介绍线程间如何进行通信的问题,当时使用的是等待通知模型,这篇文章介绍一个java提供的用于两个线程间通信的工具类Exchanger。

一、概念理解


Exchanger的作用就是为了两个线程之间交换数据,他提供了一个内部方法exchange,这个内部方法就好比是一个同步点,只有两个方法都到达同步点,才可以交换数据。我们换一张图来演示一波。

v2-a9664bb8a0ca8f01248251d68bb04735_1440w.jpg也就是说只有线程A和线程B都到达同步点,才可以交换数据。


我们上代码直接看看如何使用,然后再去看看使用的时候需要注意什么。


二、使用案例


1、基本使用


首先我们定义一个测试类ExchangerTest:

public class ExchangerTest {
    private static Exchanger<String> exchanger = new Exchanger<>();
    private static String threadA_data = "100块";
    private static String threadB_data = "50块";
    public static void main(String[] args) {
        new ThreadA(exchanger, threadA_data).start();
        new ThreadB(exchanger, threadB_data).start();
    }
}

在这个类中,我们使用了ThreadA和ThreadB两个线程交换数据,然后我们定义了一个交换器Exchanger来交换。下面我们看看这俩线程是如何实现的。

public class ThreadA extends Thread {
    private Exchanger<String> exchanger = new Exchanger<>();
    private String data = null;
    public ThreadA(Exchanger<String> exchanger, String data) {
        this.exchanger = exchanger;
        this.data = data;
    }
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("线程A交换前的数据是:"+data);
            data = exchanger.exchange(data);
            System.out.println("线程A交换后的数据是:"+data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里我们主要是看run方法的实现,首先我们打印出交换之前的数据信息,然后使用交换器交换数据,最后再打印出交换之后的数据。由于ThreadB和ThreadA实现方式一样,在这里我们只给出一份代码即可。下面我们就可以运行一下,看看测试结果:

v2-19732b6a79df5d1b2e0fc0391469cc73_1440w.jpg

现在我们看到,线程A和线程B就可以正常的进行交换了。通过这个案例我们会发现,Exchanger使用起来真的是超级简单。不过看起来很简单,其实还挖了很多的坑,下面我们来看看。


注意点一:两个线程最终必须到达同步点


这是什么意思呢?我们画一张图,举一个例子。

v2-c46ae50381bd62d8243b24c6ec85612d_1440w.jpg

上面这张图的意思是这个样子的,左边的线程还有20秒才可以到达同步点,但是右边的线程设置了超时时间,如果10秒钟后对方没有到达,那么这次交易就宣告失败。对于我们的程序来说也会出现异常。我们代码演示一下:


首先这次我们看右边的线程A:设置了超时时间为10秒

public class ThreadA extends Thread {
    private Exchanger<String> exchanger = new Exchanger<>();
    private String data = null;
    public ThreadA(Exchanger<String> exchanger, String data) {
        this.exchanger = exchanger;
        this.data = data;
    }
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("线程A交换前的数据是:"+data);
            //线程A:设置超时时间为10秒,对应于右边的线程
            data = exchanger.exchange(data,10,TimeUnit.SECONDS);
            System.out.println("线程A交换后的数据是:"+data);
        } catch (InterruptedException | TimeoutException e) {
            e.printStackTrace();
        }
    }
}

然后就是左边的线程B:还需要20秒才可以抵达

public class ThreadB extends Thread {
    private Exchanger<String> exchanger = new Exchanger<>();
    private String data = null;
    public ThreadB(Exchanger<String> exchanger, String data) {
        this.exchanger = exchanger;
        this.data = data;
    }
    @Override
    public void run() {
        try {
            //我还有20秒才可以抵达
            TimeUnit.SECONDS.sleep(20);
            System.out.println("线程B交换后的数据hashcode是:"+data.hashCode());
            data = exchanger.exchange(data);
            System.out.println("线程B交换后的数据hashcode是:"+data.hashCode());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

现在我们再去测试一下看看会出现什么结果:v2-94fa14620fdfa85980f9448c226fa087_1440w.jpg

我们发现线程A等待了10秒之后,线程B还没有到达,那就宣告交易失败。程序出现超时异常。


注意点二:交换的线程必须成对出现


这个注意点是什么意思呢?其实就是不能是单,就好比是找对象,最后总是成双成对的,要是5个男的4个女的,那剩下的一个男同胞怎么办,只能在那傻等了。这个我们也可以代码测试一下,只是新增了一个线程C。测试代码变一下:

public class ExchangerTest3 {
    private static Exchanger<String> exchanger = new Exchanger<>();
    private static String threadA_data = "100块";
    private static String threadB_data = "50块";
    private static String threadC_data = "10块";
    public static void main(String[] args) {
        new ThreadA(exchanger, threadA_data).start();
        new ThreadB(exchanger, threadB_data).start();
        new ThreadC(exchanger, threadC_data).start();
    }
}

此时我们再去测试,就会发现,总有一个线程处于死循环一直等待的状态。

v2-db4d83ebc3ec44ae2ed406f3d51ff1ef_1440w.jpg

注意点三:多个线程交换数据


上面我们提到了交换的线程配对之后不能落单,那么如果此时有多个成对的线程了,谁和谁配对呢?答案我们先告诉你,那就是胡乱配对。


在这里我们在注意点二的基础之上继续增加一个线程D,然后继续更改我们的测试类运行一下:v2-b13cba27f5afda83aa74b9ab3cf1c335_1440w.jpg

对于Exchanger的使用基本上需要注意的就是这么多。希望对你有帮助。




相关文章
|
3天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
22 0
|
1天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
1天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
2天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
6 0
|
2天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
3天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
16 0
|
3天前
|
监控 安全 Java
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
10 2
|
3天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
28 1
|
3天前
|
安全 Java
Java基础教程(15)-多线程基础
【4月更文挑战第15天】Java内置多线程支持,通过Thread类或Runnable接口实现。线程状态包括New、Runnable、Blocked、Waiting、Timed Waiting和Terminated。启动线程调用start(),中断线程用interrupt(),同步用synchronized关键字。线程安全包如java.util.concurrent提供并发集合和原子操作。线程池如ExecutorService简化任务管理,Callable接口允许返回值,Future配合获取异步结果。Java 8引入CompletableFuture支持回调。
|
3天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。