1. 程式人生 > 其它 >java執行緒間通訊的幾種方式

java執行緒間通訊的幾種方式

技術標籤:面試併發程式設計多執行緒併發程式設計面試

併發程式設計中,我們可能會遇到這樣一個場景

A、B兩個執行緒並行,但是我希望保證B執行緒在A執行緒執行完了後再執行

這個時候就需要執行緒間進行通訊

A執行完了後對B說一聲,喂B,我執行完了

來康康用Java怎麼實現

1、基於synchronized
2、基於reentrantLock
3、基於volatile
4、基於countDownLatch

我目前就知道這四種

1、synchronized+wait() 和 notify()

wait() 和 notify()都是Object類的通訊方法,注意一點,wait和 notify必須搭配synchronized使用,並且wait()會釋放鎖,notify()不會釋放鎖

public class SynchronizedTest {

    //定義個year,用來記錄某明星的練習年數
    private static double year;

    public void run() {
        //執行緒A,練習唱跳rap
        Thread threadA = new Thread(() -> {
            synchronized (this) {
                for (year = 0.5; year <= 5; year += 0.5) {
                    System.
out.println("蔡徐雞開始練習唱跳rap:已練習" + year + "年"); try { Thread.sleep(288); } catch (InterruptedException e) { e.printStackTrace(); } //眾所周知,練習兩年半即可出道 if
(year == 2.5) { System.out.println("===========================>成功練習兩年半,出道!!!"); this.notify(); } } } }); //執行緒B,練習打籃球 Thread threadB = new Thread(() -> { while (true) { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("蔡徐雞開始練習打籃球"); } } }); //注意,一定要先啟動B,不然會導致B永遠拿不到鎖 threadB.start(); threadA.start(); } public static void main(String[] args) { SynchronizedTest test = new SynchronizedTest(); test.run(); } }

執行結果:

蔡徐雞開始練習唱跳rap:已練習0.5年
蔡徐雞開始練習唱跳rap:已練習1.0年
蔡徐雞開始練習唱跳rap:已練習1.5年
蔡徐雞開始練習唱跳rap:已練習2.0年
蔡徐雞開始練習唱跳rap:已練習2.5年
===========================>成功練習兩年半,出道!!!
蔡徐雞開始練習唱跳rap:已練習3.0年
蔡徐雞開始練習唱跳rap:已練習3.5年
蔡徐雞開始練習唱跳rap:已練習4.0年
蔡徐雞開始練習唱跳rap:已練習4.5年
蔡徐雞開始練習唱跳rap:已練習5.0年
蔡徐雞開始練習打籃球

注意看執行結果,執行緒A在執行notify後並沒有釋放鎖,而是執行完當前任務才開始執行執行緒B的任務

2、基於ReentrantLock

ReentrantLock也能實現執行緒間通訊,不過有點麻煩,需要結合ReentrantLock的Condition

public class LockTest {
        //定義個year,用來記錄某明星練習打籃球的年數
        private static double year;

        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
            //執行緒A,練習唱跳rap
            Thread threadA = new Thread(() -> {
                //執行業務程式碼前上鎖
                lock.lock();
                for (year = 0.5; year <= 5; year += 0.5) {
                    System.out.println("蔡徐雞開始練習唱跳rap:已練習" + year + "年");
                    try {
                        Thread.sleep(288);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //眾所周知,練習兩年半即可出道
                    if (year == 2.5) {
                        System.out.println("===========================>成功練習兩年半,出道!!!");
                        //喚醒等待中的執行緒
                        condition.signal();
                    }
                }
                //業務程式碼執行完後解鎖
                lock.unlock();
            });
            //執行緒B,練習打籃球
            Thread threadB = new Thread(() -> {
                //執行業務程式碼前上鎖
                lock.lock();
                while (true) {
                    try {
                        //讓執行緒等待,如果計數器為0的話,則立即執行
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("蔡徐老母雞開始練習打籃球");
                    break;
                }
                //業務程式碼執行完後解鎖
                lock.unlock();
            });
            //注意,一定要先啟動B,不然會導致B永遠拿不到鎖
            threadB.start();
            threadA.start();
        }
    }

執行結果:

蔡徐雞開始練習唱跳rap:已練習0.5年
蔡徐雞開始練習唱跳rap:已練習1.0年
蔡徐雞開始練習唱跳rap:已練習1.5年
蔡徐雞開始練習唱跳rap:已練習2.0年
蔡徐雞開始練習唱跳rap:已練習2.5年
===========================>成功練習兩年半,出道!!!
蔡徐雞開始練習唱跳rap:已練習3.0年
蔡徐雞開始練習唱跳rap:已練習3.5年
蔡徐雞開始練習唱跳rap:已練習4.0年
蔡徐雞開始練習唱跳rap:已練習4.5年
蔡徐雞開始練習唱跳rap:已練習5.0年
蔡徐老母雞開始練習打籃球

效果和synchronized+wait() 和 notify()一樣一樣的

3、基於volatile

使用共享變數也能實現,用volatile即可,原理就是多個執行緒共同監聽同個變數,根據變數的值變化來執行對應的任務,此處volatile的作用就是讓其它執行緒能即時感知變數值的改變

public class volatileTest {
    //定義一個共享變數,注意,必須用volatile修飾
    static volatile boolean flag = false;
    //定義個year,用來記錄某明星練習打籃球的年數
    private static double year;

    public static void main(String[] args) {
        //執行緒A,練習唱跳rap
        Thread threadA = new Thread(() -> {
            while (true) {
                if (!flag) {
                    for (year = 0.5; year <= 5; year += 0.5) {
                        System.out.println("蔡徐雞開始練習唱跳rap:已練習" + year + "年");
                        try {
                            Thread.sleep(288);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //眾所周知,練習兩年半即可出道
                        if (year == 2.5) {
                            System.out.println("===========================>成功練習兩年半,出道!!!");
                            year = 0.5;
                            flag = true;
                            break;
                        }
                    }
                }
            }
        });
        //執行緒B,練習打籃球
        Thread threadB = new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println("蔡徐老母雞開始練習打籃球");
                    break;
                }
            }
        });
        // 啟動執行緒
        threadA.start();
        threadB.start();
    }
}

執行結果:

蔡徐雞開始練習唱跳rap:已練習0.5年
蔡徐雞開始練習唱跳rap:已練習1.0年
蔡徐雞開始練習唱跳rap:已練習1.5年
蔡徐雞開始練習唱跳rap:已練習2.0年
蔡徐雞開始練習唱跳rap:已練習2.5年
===========================>成功練習兩年半,出道!!!
蔡徐老母雞開始練習打籃球

基於CountDownLatch

CountDownLatch是JUC包下的一個併發程式設計工具,主要有兩個方法,countDown和await,CountDownLatch底層維護了一個計數器,在例項化的時候設定,當呼叫countDown方法時,計數器減一,如果計數器在減一前已經為0,那麼什麼都不會發生,如果減一後變成0,則喚醒所有等待的執行緒;await方法會使當前執行緒等待,直到計數器為0

public class CountDownLatchTest {
    //定義個year,用來記錄某明星練習打籃球的年數
    private static double year;

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(1);
        //執行緒A,練習唱跳rap
        Thread threadA = new Thread(() -> {
            for (year = 0.5; year <= 5; year += 0.5) {
                System.out.println("蔡徐雞開始練習唱跳rap:已練習" + year + "年");
                try {
                    Thread.sleep(288);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //眾所周知,練習兩年半即可出道
                if (year == 2.5) {
                    System.out.println("===========================>成功練習兩年半,出道!!!");
                    //計數器減一
                    latch.countDown();
                }
            }
        });
        //執行緒B,練習打籃球
        Thread threadB = new Thread(() -> {
            while (true) {
                try {
                    //讓執行緒等待,如果計數器為0的話,則立即執行
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("蔡徐老母雞開始練習打籃球");
                break;
            }
        });
        // 啟動執行緒
        threadA.start();
        threadB.start();
    }
}

執行結果:

蔡徐雞開始練習唱跳rap:已練習0.5年
蔡徐雞開始練習唱跳rap:已練習1.0年
蔡徐雞開始練習唱跳rap:已練習1.5年
蔡徐雞開始練習唱跳rap:已練習2.0年
蔡徐雞開始練習唱跳rap:已練習2.5年
===========================>成功練習兩年半,出道!!!
蔡徐雞開始練習唱跳rap:已練習3.0年
蔡徐老母雞開始練習打籃球
蔡徐雞開始練習唱跳rap:已練習3.5年
蔡徐雞開始練習唱跳rap:已練習4.0年
蔡徐雞開始練習唱跳rap:已練習4.5年
蔡徐雞開始練習唱跳rap:已練習5.0年

如果你多執行幾次,你會發現執行緒B執行的時機是隨機的,但永遠在計數器為0後才開始執行,也就是說計數器為0後,執行緒A和執行緒B誰搶到鎖就誰執行


文中所有demo都是複製即可執行,大家還是要多動手,家裡有條件的都用idea跑一跑,沒條件的可以用手抄

嚶~

ok我話說完