天天看點

Java多線程(Thread)間通信和共享資料

        多線程間如何通信和共享資料,肯定好多小夥伴都不知道, 或者很久之前知道,現在已經忘卻,現在我和大家一起複習一下。

        一、線程間通信

        1、線程間通信:A執行完,B才執行

/**
     * 線程間通信:A執行完,B才執行
     */
    @Test
    public void bWaitA(){
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A 開始工作!!");
                System.out.println("A 結束工作!!");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 開始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B 開始工作!!");
                System.out.println("B 結束工作!!");
            }
        });
        B.start();
        A.start();
    }
           

        2、A執行一部分,B執行完,A再執行剩下的部分

/**
     * A執行一部分,B執行完,A再執行剩下的部分
     * A 1, B 1, B 2, B 3, A 2, A 3
     *
     * 首先建立一個 A 和 B 共享的對象鎖 lock = new Object();
     * 當 A 得到鎖後,先列印 1,然後調用 lock.wait() 方法,交出鎖的控制權,進入 wait 狀态;
     * 對 B 而言,由于 A 最開始得到了鎖,導緻 B 無法執行;直到 A 調用 lock.wait() 釋放控制權後, B 才得到了鎖;
     * B 在得到鎖後列印 1, 2, 3;然後調用 lock.notify() 方法,喚醒正在 wait 的 A;
     * A 被喚醒後,繼續列印剩下的 2,3。
     */
    @Test
    public void aWaitB() {
        Object lock = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A 等待鎖");
                synchronized (lock) {
                    System.out.println("A 獲得鎖");
                    System.out.println("A 1");
                    try {
                        System.out.println("A wait 放棄鎖控制權,等待再次擷取鎖控制權");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("被喚醒,重新擷取鎖控制權");
                    System.out.println("A 2");
                    System.out.println("A 3");
                }
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 等待擷取鎖控制權");
                synchronized (lock) {
                    System.out.println("B 擷取鎖控制權");
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");
                    System.out.println("B 執行完畢 調用 notiry 方法");
                    lock.notify();
                }
            }
        });
        A.start();
        B.start();
    }
           

        3、四個線程 A B C D,其中 D 要等到 A B C 全執行完畢後才執行,而且 A B C 是同步運作的

/**
     * 四個線程 A B C D,其中 D 要等到 A B C 全執行完畢後才執行,而且 A B C 是同步運作的
     *
     * CountdownLatch 來實作這類通信方式。它的基本用法是:
     * 建立一個計數器,設定初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
     * 在 等待線程 裡調用 countDownLatch.await() 方法,進入等待狀态,直到計數值變成 0;
     * 在 其他線程 裡,調用 countDownLatch.countDown() 方法,該方法會将計數值減小 1;
     * 當 其他線程 的 countDown() 方法把計數值變成 0 時,等待線程 裡的 countDownLatch.await() 立即退出,繼續執行下面的代碼。
     *
     * 執行過程:
     * 其實簡單點來說,CountDownLatch 就是一個倒計數器,我們把初始計數值設定為3,
     * 當 D 運作時,先調用 countDownLatch.await() 檢查計數器值是否為 0,若不為 0 則保持等待狀态;
     * 當A B C 各自運作完後都會利用countDownLatch.countDown(),将倒計數器減 1,
     * 當三個都運作完後,計數器被減至 0;此時立即觸發 D 的 await() 運作結束,繼續向下執行。
     * 是以,CountDownLatch 适用于一個線程去等待多個線程的情況。
     */
    @Test
    public void dWaitABC() {
        int worker = 3;
        CountDownLatch countDownLatch = new CountDownLatch(worker);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D 等待其他線程執行完畢");
                try {
                    countDownLatch.await();
                    System.out.println("所有需要等待的線程都執行完畢,D開始工作");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for (char threadName='A'; threadName <= 'C'; threadName++) {
            final String threadNameStr = String.valueOf(threadName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(threadNameStr + " 正在工作");
                    System.out.println(threadNameStr + " 工作完成");
                    countDownLatch.countDown();
                }
            }).start();
        }
    }
           

        4、個運動員各自準備,等到三個人都準備好後,再一起跑

/**
     *  三個運動員各自準備,等到三個人都準備好後,再一起跑
     *
     * CyclicBarrier
     * 為了實作線程間互相等待這種需求,我們可以利用 CyclicBarrier 資料結構,它的基本用法是:
     * 先建立一個公共 CyclicBarrier 對象,設定 同時等待 的線程數,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
     * 這些線程同時開始自己做準備,自身準備完畢後,需要等待别人準備完畢,這時調用 cyclicBarrier.await(); 即可開始等待别人;
     * 當指定的 同時等待 的線程數都調用了 cyclicBarrier.await();時,意味着這些線程都準備完畢好,然後這些線程才 同時繼續執行。
     */
    @Test
    public void runABCWhenAllReady() {
        int runner = 3;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
        final Random random = new Random();
        for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
            final String runnerNameStr = String.valueOf(runnerName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long prepareTime = random.nextInt(1000) + 100;
                    System.out.println(runnerNameStr + " 線程正在做準備 " );
                    try {
                        Thread.sleep(prepareTime);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println(runnerNameStr + " 線程準備完畢等待其他線程準備好");
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println("所有線程都準備好了,"+runnerNameStr +" 線程開始工作");
                }
            }).start();
        }
        // 讓主線程休眠,讓裡面的線程執行完畢之前,主線程還存在
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
           

        二、線程資料共享

/**
     * 線程間資料共享
     * 不同的線程,都有一個屬性 ShareData ,并且線程的工作也封裝 ShareData中。
     * 不同線程中的run方法執行的都是 ShareData 中指定的封裝的方法
     */
    @Test
    public void shareData(){
        ShareData shareData = new ShareData();
        for(int i=1;i<=2;i++){
            new Thread(new MyIncreRunnable(shareData)).start();
            new Thread(new MyDecreRunanble(shareData)).start();
        }
    }
           
/**
     * 線程間資料共享
     * 不同的線程,都有一個屬性 ShareData ,并且線程的工作也封裝 ShareData中。
     * 不同線程中的run方法執行的都是 ShareData 中指定的封裝的方法
     */
    @Test
    public void shareData(){
        ShareData shareData = new ShareData();
        for(int i=1;i<=2;i++){
            new Thread(new MyIncreRunnable(shareData)).start();
            new Thread(new MyDecreRunanble(shareData)).start();
        }
    }


class MyIncreRunnable implements Runnable{
    private ShareData shareData;
    public MyIncreRunnable(ShareData shareData) {
        this.shareData = shareData;
    }
    @Override
    public void run() {
        for (int i = 1;i<=10;i++){
            shareData.increment();
        }
    }
}

class MyDecreRunanble implements Runnable{
    private ShareData shareData;
    public MyDecreRunanble(ShareData shareData) {
        this.shareData = shareData;
    }
    @Override
    public void run() {
        for(int i=1;i<=10;i++){
            shareData.decrement();
        }
    }
}

/**
 * 共享資料,對共享資料的操作也在這個對象中完成
 */
class ShareData{
    private int count = 0;
    public synchronized void increment(){
        count++;
        System.out.println(Thread.currentThread().getName()+" inc "+count);
    }
    public synchronized void decrement(){
        count--;
        System.out.println(Thread.currentThread().getName()+" dec " +count);
    }
}