最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)線程間的通信最佳實(shí)踐

 更新時(shí)間:2026年03月25日 08:55:43   作者:左左右右左右搖晃  
這篇文章主要介紹了Java并發(fā)線程間的通信最佳實(shí)踐,本文將帶你全面了解線程間通信的原理、常見(jiàn)陷阱以及如何優(yōu)雅地實(shí)現(xiàn)線程協(xié)作,需要的朋友可以參考下

在多線程編程中,線程間通信是一個(gè)核心話題。當(dāng)多個(gè)線程需要協(xié)同完成某個(gè)任務(wù)時(shí),它們必須能夠互相通知狀態(tài)的變化,以避免競(jìng)態(tài)條件和無(wú)效的資源占用。Java提供了多種線程間通信的方式,從最基礎(chǔ)的 wait/notify 機(jī)制,到 Lock 配合 Condition 的靈活方案。本文將帶你全面了解線程間通信的原理、常見(jiàn)陷阱以及如何優(yōu)雅地實(shí)現(xiàn)線程協(xié)作。

一、線程間通信的必要性

思考一個(gè)簡(jiǎn)單的場(chǎng)景:兩個(gè)線程操作一個(gè)共享變量,一個(gè)線程負(fù)責(zé)加1,另一個(gè)線程負(fù)責(zé)減1,要求交替執(zhí)行10輪。如果沒(méi)有通信機(jī)制,線程A可能連續(xù)加多次,線程B才減一次,導(dǎo)致結(jié)果混亂。線程間通信正是為了解決這類問(wèn)題——讓線程在合適的時(shí)機(jī)暫停和喚醒,從而保證操作的順序性和數(shù)據(jù)的一致性。

二、傳統(tǒng)的wait/notify機(jī)制

2.1 基本使用

Java中每個(gè)對(duì)象都有一組監(jiān)視器方法:wait()、notify()、notifyAll()。它們必須在同步塊(synchronized)中使用,因?yàn)樾枰@取對(duì)象的監(jiān)視器鎖。

下面是一個(gè)經(jīng)典的“生產(chǎn)者-消費(fèi)者”示例,兩個(gè)線程交替對(duì)變量進(jìn)行+1和-1操作:

class ShareData {
    private int number = 0;
    public synchronized void increment() throws InterruptedException {
        // 1. 判斷
        if (number != 0) {
            this.wait();
        }
        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        // 3. 通知
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        if (number != 1) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " => " + number);
        this.notifyAll();
    }
}
public class WaitNotifyDemo {
    public static void main(String[] args) {
        ShareData data = new ShareData();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }, "B").start();
    }
}

運(yùn)行結(jié)果會(huì)交替輸出 A => 1 和 B => 0,共10輪。這里的關(guān)鍵點(diǎn)在于:

  • 線程在執(zhí)行操作前,先判斷條件是否滿足(number是否為0或1)。
  • 不滿足則調(diào)用 wait() 進(jìn)入等待狀態(tài),同時(shí)釋放鎖。
  • 操作完成后,調(diào)用 notifyAll() 喚醒所有等待的線程。

2.2 虛假喚醒問(wèn)題

當(dāng)我們將線程數(shù)增加到4個(gè)(兩個(gè)加線程,兩個(gè)減線程),并運(yùn)行多次后,可能會(huì)看到 2、3 等異常值,甚至出現(xiàn)負(fù)數(shù)。這是因?yàn)?nbsp;if 判斷導(dǎo)致的虛假喚醒。

虛假喚醒指的是線程被喚醒后,條件可能已經(jīng)不再滿足,但程序仍然繼續(xù)執(zhí)行。例如,當(dāng) number 為0時(shí),A1和A2都等待在 increment 方法中;當(dāng)B執(zhí)行減1后調(diào)用 notifyAll(),A1和A2同時(shí)被喚醒,它們都從 wait() 后繼續(xù)執(zhí)行,導(dǎo)致 number 被連續(xù)加了兩次,變?yōu)?。

解決方案:將 if 改為 while,使線程被喚醒后重新檢查條件。這是JDK文檔明確要求的。

public synchronized void increment() throws InterruptedException {
    while (number != 0) {  // 使用while
        this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName() + " => " + number);
    this.notifyAll();
}

2.3 wait/notify的局限性

  • 無(wú)法精確喚醒notifyAll() 會(huì)喚醒所有等待線程,增加了不必要的上下文切換;notify() 只喚醒一個(gè),但無(wú)法指定喚醒哪一個(gè)。
  • 必須與synchronized綁定:只能配合 synchronized 使用,不夠靈活。
  • 無(wú)法響應(yīng)中斷wait() 會(huì)拋出 InterruptedException,但線程無(wú)法在等待期間主動(dòng)中斷。

三、Lock + Condition:更靈活的通信方式

從JDK 1.5開(kāi)始,java.util.concurrent.locks 包提供了 Lock 接口和 Condition 接口,彌補(bǔ)了 wait/notify 的不足。

3.1 Condition的基本用法

每個(gè) Condition 對(duì)象都相當(dāng)于一個(gè)“隊(duì)列”,通過(guò) await() 和 signal()/signalAll() 實(shí)現(xiàn)線程的等待與喚醒。與 wait/notify 類似,使用前必須先獲取對(duì)應(yīng)的鎖。

將上面的例子用 ReentrantLock 和 Condition 改寫(xiě):

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData {
    private int number = 0;
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + " => " + number);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number != 1) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " => " + number);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

相比 synchronized,Lock 提供了更多控制能力(如 tryLock、可中斷鎖等),而 Condition 則可以創(chuàng)建多個(gè)等待隊(duì)列,實(shí)現(xiàn)精確喚醒

3.2 多個(gè)Condition實(shí)現(xiàn)精準(zhǔn)通信

需求:三個(gè)線程 A、B、C 依次執(zhí)行,A 打印5次,B 打印10次,C 打印15次,循環(huán)10輪。

這種場(chǎng)景下,需要在線程A執(zhí)行完后精確喚醒B,B執(zhí)行完后精確喚醒C,C執(zhí)行完后精確喚醒A。通過(guò)為每個(gè)線程創(chuàng)建一個(gè) Condition 對(duì)象,并結(jié)合一個(gè)狀態(tài)標(biāo)識(shí),即可輕松實(shí)現(xiàn)。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource {
    private int flag = 1; // 1: A, 2: B, 3: C
    private final Lock lock = new ReentrantLock();
    private final Condition conditionA = lock.newCondition();
    private final Condition conditionB = lock.newCondition();
    private final Condition conditionC = lock.newCondition();
    public void print5() {
        lock.lock();
        try {
            while (flag != 1) {
                conditionA.await();
            }
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + " => " + i);
            }
            flag = 2;
            conditionB.signal(); // 喚醒B
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print10() {
        lock.lock();
        try {
            while (flag != 2) {
                conditionB.await();
            }
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + " => " + i);
            }
            flag = 3;
            conditionC.signal(); // 喚醒C
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print15() {
        lock.lock();
        try {
            while (flag != 3) {
                conditionC.await();
            }
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + " => " + i);
            }
            flag = 1;
            conditionA.signal(); // 喚醒A
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ConditionDemo {
    public static void main(String[] args) {
        ShareResource resource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) resource.print5();
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) resource.print10();
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) resource.print15();
        }, "C").start();
    }
}

這樣,每個(gè)線程只會(huì)在屬于自己的標(biāo)識(shí)位被設(shè)置時(shí)才執(zhí)行,執(zhí)行完后精確喚醒下一個(gè)線程,避免了無(wú)效的喚醒競(jìng)爭(zhēng)。

四、經(jīng)典面試題:交替打印數(shù)字和字母

題目:兩個(gè)線程,一個(gè)打印1~52的數(shù)字,另一個(gè)打印A~Z的字母,要求打印結(jié)果為12A34B...5152Z。

分析:數(shù)字線程每次打印兩個(gè)數(shù)字,字母線程每次打印一個(gè)字母??梢酝ㄟ^(guò)一個(gè)標(biāo)志位來(lái)控制切換,也可以用 Condition 來(lái)實(shí)現(xiàn)精確交替。

4.1 使用 wait/notify 實(shí)現(xiàn)

class Printer {
    private int num = 1;
    private char letter = 'A';
    private boolean printNum = true;
    public synchronized void printNumber() {
        for (int i = 0; i < 26; i++) {
            while (!printNum) {
                try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
            }
            System.out.print(num++);
            System.out.print(num++);
            printNum = false;
            notifyAll();
        }
    }
    public synchronized void printLetter() {
        for (int i = 0; i < 26; i++) {
            while (printNum) {
                try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
            }
            System.out.print(letter++);
            printNum = true;
            notifyAll();
        }
    }
}
public class PrintDemo {
    public static void main(String[] args) {
        Printer printer = new Printer();
        new Thread(printer::printNumber).start();
        new Thread(printer::printLetter).start();
    }
}

4.2 使用 Condition 實(shí)現(xiàn)

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Printer {
    private int num = 1;
    private char letter = 'A';
    private boolean printNum = true;
    private final Lock lock = new ReentrantLock();
    private final Condition numberCondition = lock.newCondition();
    private final Condition letterCondition = lock.newCondition();
    public void printNumber() {
        lock.lock();
        try {
            for (int i = 0; i < 26; i++) {
                while (!printNum) {
                    numberCondition.await();
                }
                System.out.print(num++);
                System.out.print(num++);
                printNum = false;
                letterCondition.signal();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printLetter() {
        lock.lock();
        try {
            for (int i = 0; i < 26; i++) {
                while (printNum) {
                    letterCondition.await();
                }
                System.out.print(letter++);
                printNum = true;
                numberCondition.signal();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

五、總結(jié)與最佳實(shí)踐

  • 優(yōu)先使用 Lock + Condition
    • 如果需要精確控制線程喚醒順序、支持中斷或超時(shí),或者需要更靈活的鎖機(jī)制,推薦使用 ReentrantLock 和 Condition
  • 避免虛假喚醒
    • 無(wú)論使用 wait/notify 還是 Condition.await(),判斷條件時(shí)必須使用 while 循環(huán),而不是 if
  • 在 finally 中釋放鎖
  • Lock.unlock() 必須放在 finally 塊中,確保鎖在任何情況下都能被釋放,避免死鎖。
  • 使用多個(gè) Condition 實(shí)現(xiàn)精確通信
    • 當(dāng)需要多個(gè)線程協(xié)作時(shí),為每個(gè)線程創(chuàng)建獨(dú)立的 Condition,結(jié)合狀態(tài)標(biāo)志,可以顯著提高代碼的可讀性和效率。
  • 注意 notify() vs notifyAll()
    • 使用 Condition.signal() 可以精確喚醒一個(gè)等待線程,而 signalAll() 會(huì)喚醒所有等待該條件的線程。一般情況下,精確喚醒能減少不必要的上下文切換。

到此這篇關(guān)于Java并發(fā)線程間的通信最佳實(shí)踐的文章就介紹到這了,更多相關(guān)Java線程間的通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)將PDF轉(zhuǎn)為圖片格式的方法詳解

    Java實(shí)現(xiàn)將PDF轉(zhuǎn)為圖片格式的方法詳解

    PDF文件和圖片文件,這是兩種完全不一樣的格式,可是有的時(shí)候這兩種格式卻是有相互轉(zhuǎn)換的需要,本文將介紹如何通過(guò)Java應(yīng)用程序快速高效地將PDF轉(zhuǎn)為圖片格式。一起來(lái)看看吧
    2023-03-03
  • Java統(tǒng)計(jì)接口耗時(shí)的六種常見(jiàn)方法

    Java統(tǒng)計(jì)接口耗時(shí)的六種常見(jiàn)方法

    有些小伙伴在工作中,可能經(jīng)常遇到這樣的場(chǎng)景:線上接口突然變慢,用戶抱怨連連,你卻一頭霧水,不知道問(wèn)題出在哪里,其實(shí),這些問(wèn)題都離不開(kāi)一個(gè)基礎(chǔ)技能如何準(zhǔn)確統(tǒng)計(jì)接口耗時(shí),所以本文就跟大家一起聊聊統(tǒng)計(jì)接口耗時(shí)的6種常見(jiàn)方法,需要的朋友可以參考下
    2025-10-10
  • springBoot集成flowable的流程解析

    springBoot集成flowable的流程解析

    這篇文章主要介紹了springBoot集成flowable的流程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • 多模塊項(xiàng)目使用枚舉配置spring-cache緩存方案詳解

    多模塊項(xiàng)目使用枚舉配置spring-cache緩存方案詳解

    這篇文章主要為大家介紹了多模塊項(xiàng)目使用枚舉配置spring-cache緩存的方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Java 8 Stream操作類型及peek示例解析

    Java 8 Stream操作類型及peek示例解析

    這篇文章主要介紹了Java 8 Stream操作類型及peek示例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • JAVA遞歸生成樹(shù)形菜單的實(shí)現(xiàn)過(guò)程

    JAVA遞歸生成樹(shù)形菜單的實(shí)現(xiàn)過(guò)程

    這篇文章主要介紹了JAVA遞歸生成樹(shù)形菜單,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • mybatis 在typeAliases別名時(shí)報(bào)錯(cuò)的解決

    mybatis 在typeAliases別名時(shí)報(bào)錯(cuò)的解決

    這篇文章主要介紹了mybatis 在typeAliases別名時(shí)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 實(shí)例講解Java處理PDF圖章的方法

    實(shí)例講解Java處理PDF圖章的方法

    在本篇文章里小編給大家分享了關(guān)于Java處理PDF圖章的方法,對(duì)此有需要的朋友們可以學(xué)習(xí)下。
    2019-02-02
  • redis.clients.jedis.exceptions.JedisMovedDataException異常解決

    redis.clients.jedis.exceptions.JedisMovedDataException異常解決

    redis.clients.jedis.exceptions.JedisMovedDataException?異常是在使用?Jedis?客戶端與?Redis?集群進(jìn)行交互時(shí)發(fā)生的,下面就來(lái)介紹一下解決方法,感興趣的可以了解一下
    2024-05-05
  • Apache?Maven3.6.0的下載安裝和環(huán)境配置(圖文教程)

    Apache?Maven3.6.0的下載安裝和環(huán)境配置(圖文教程)

    本文主要介紹了Apache?Maven3.6.0的下載安裝和環(huán)境配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評(píng)論

昂仁县| 柯坪县| 深水埗区| 通山县| 建始县| 岳普湖县| 厦门市| 西安市| 武义县| 鹤峰县| 林周县| 华亭县| 乐亭县| 海伦市| 泰顺县| 泗洪县| 绥德县| 宽甸| 彭水| 赞皇县| 高雄市| 普洱| 万盛区| 惠州市| 固始县| 肥西县| 彩票| 中卫市| 河源市| 文成县| 东宁县| 米林县| 德保县| 新昌县| 永宁县| 监利县| 高碑店市| 澄江县| 蒙山县| 固阳县| 吴堡县|