開發與維運

一文理解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的使用基本上需要注意的就是這麼多。希望對你有幫助。

Leave a Reply

Your email address will not be published. Required fields are marked *