成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java并發編程:線程安全

開發 前端
通俗地說,無論有多少線程訪問業務中的一個對象或方法,在編寫這段業務邏輯時,無需做任何額外處理(即可以像單線程程序一樣編寫),程序也能正常運行(不會因多線程而失敗),這樣的代碼就可以稱為線程安全的。

1. 什么是線程安全?

《Java 并發編程實戰》的作者 Brian Goetz 對線程安全的理解是:當多個線程訪問一個對象時,如果不需要考慮這些線程在運行時環境中的調度和交替執行,也不需要額外的同步,調用這個對象的行為都能獲得正確的結果,那么這個對象就是線程安全的。

通俗地說,無論有多少線程訪問業務中的一個對象或方法,在編寫這段業務邏輯時,無需做任何額外處理(即可以像單線程程序一樣編寫),程序也能正常運行(不會因多線程而失敗),這樣的代碼就可以稱為線程安全的。

2. 什么是線程不安全?

當多個線程同時訪問一個對象時,如果某個線程正在更新對象的值,而另一個線程同時讀取該對象的值,就可能導致獲取到錯誤的值。這種情況下,我們需要采取額外措施(例如使用synchronized關鍵字同步這部分代碼的執行)來確保結果的正確性。

3. 為什么不是所有程序都設計成線程安全的?

主要是出于程序性能、設計復雜度成本等方面的考量。

4. 線程安全問題的分類

4.1 運行結果錯誤

首先來看多線程同時操作一個變量如何導致運行結果錯誤。

假設用兩個線程對count變量進行計數,每個線程各計 10000 次:

public class ResultError {
    static int count;
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        };
        Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

輸出:

圖片圖片

理論上結果應為 20000,但實際輸出遠小于理論值,且每次結果不同。為什么會這樣?

這是因為多線程下,CPU 的調度是以時間片為單位分配的,每個線程獲得一定時間片后,若時間片耗盡會被掛起并讓出 CPU 資源給其他線程,這可能導致線程安全問題。例如,i++看似一行代碼,實際并非原子操作,其執行步驟主要分為三步,且每一步操作之間可能被中斷:

  1. 讀取當前值;
  2. 遞增;
  3. 保存結果。

圖片圖片

假設線程 1 先讀取count=1,隨后執行count + 1操作,但此時結果尚未保存,線程 1 被切換。CPU 開始執行線程 2,其操作與線程 1 相同。但此時線程 2 讀取的count值是多少?由于線程 1 的+1操作未保存結果,線程 2 讀取的仍然是count=1。

假設線程 2 執行count + 1后保存結果為 2,隨后線程 1 恢復執行,保存其計算結果為 2。雖然兩個線程各執行了一次+1,但最終count結果為 2 而非預期的 3。這就是典型的線程安全問題,此時count變量被稱為共享變量或共享數據。

如何解決?

解決此類問題需要一種機制:當多個線程操作共享變量時,確保同一時刻僅有一個線程能操作該變量,其他線程必須等待當前線程處理完成。這種方法使用互斥鎖(Mutex Lock)實現互斥訪問——當共享數據被當前線程加鎖時,其他線程只能等待鎖釋放。

Java 中,用synchronized關鍵字修飾的方法或代碼塊可以保證同一時刻僅有一個線程執行。代碼如下:

public class ResultErrorResolution {
    staticint count;
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            synchronized (ResultErrorResolution.class) {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

輸出:

20000

輸出結果與預期一致??。

關于synchronized關鍵字,后續章節會詳細講解。目前只需知道它能保證同一時刻最多一個線程執行該代碼段(需持有對應的鎖,本例中為ResultErrorResolution.class),從而實現并發安全。

4.2 線程活躍性問題

第二類線程安全問題統稱為活躍性問題。活躍性問題指程序無法獲得運行的最終結果。相比前文的錯誤,活躍性問題的后果可能更嚴重,例如死鎖會導致程序完全卡死。

典型的活躍性問題包括死鎖(Deadlock)、活鎖(Livelock)和饑餓(Starvation)。由于內容較多,后續會單獨寫篇文章介紹。

4.3 對象初始化時的安全問題

最后是對象初始化過程中引發的線程安全問題。創建對象以供其他類或對象使用是常見操作,但若時機或錯誤可能導致線程安全問題。

看一個例子:

public class InitError {
    private Map<Long, String> students;

    public InitError() {
        new Thread(() -> {
            students = new HashMap<>();
            students.put(1L, "Tom");
            students.put(2L, "Bob");
            students.put(3L, "Victor");
        }).start();
    }

    public Map<Long, String> getStudents() {
        return students;
    }

    public static void main(String[] args) throws InterruptedException {
        InitError initError = new InitError();
        System.out.println(initError.getStudents().get(1L));
    }
}

此例中,成員變量students在構造函數的子線程中初始化。但主線程在初始化InitError后未等待子線程完成,直接嘗試獲取數據,導致問題:

public static void main(String[] args) throws InterruptedException {
    InitError initError = new InitError();
    System.out.println(initError.getStudents().get(1L));
}

運行結果:

Exception in thread "main" java.lang.NullPointerException
    at concurrency.chapter10.InitError.main(InitError.java:25)

原因:

students在構造函數的新線程中初始化,而主線程未等待該線程完成就直接調用getStudents(),此時students可能尚未初始化(返回null),導致空指針異常。

5. 哪些場景需特別注意線程安全問題?

5.1 訪問共享變量或資源

當訪問靜態變量、共享緩存等共享資源時,若多線程同時操作(如count++),需確保原子性。例如以下“檢查后執行”操作可能被中斷:

if (count == 10) {
    count = count * 10;
}

多個線程可能同時滿足count == 10,導致多次執行count = count * 10,需通過加鎖保證原子性。

5.2 數據間存在綁定關系

當不同數據成組出現且需保持對應關系時(如 IP 和端口號),若修改未綁定為一個原子操作,可能導致信息不一致。例如僅修改 IP 而未同步修改端口號,接收方可能獲取錯誤的綁定結果。

5.3 依賴的類未聲明線程安全

若使用的類未聲明自身是線程安全的(如ArrayList),在多線程并發操作時可能引發線程安全問題。責任不在該類本身,因其未做任何線程安全保證(源碼注釋中通常會說明)。

責任編輯:武曉燕 來源: 程序猿技術充電站
相關推薦

2011-12-29 13:31:15

Java

2025-02-17 00:00:25

Java并發編程

2023-10-18 09:27:58

Java編程

2025-01-10 07:10:00

2025-02-06 03:14:38

2019-11-07 09:20:29

Java線程操作系統

2024-12-31 09:00:12

Java線程狀態

2021-03-05 13:46:56

網絡安全遠程線程

2025-02-03 08:23:33

2023-10-08 09:34:11

Java編程

2023-10-18 15:19:56

2022-11-09 09:01:08

并發編程線程池

2019-09-16 08:45:53

并發編程通信

2025-07-03 07:10:00

線程池并發編程代碼

2022-03-31 07:52:01

Java多線程并發

2025-02-03 00:40:00

線程組Java并發編程

2017-01-10 13:39:57

Python線程池進程池

2023-09-26 10:30:57

Linux編程

2017-09-19 14:53:37

Java并發編程并發代碼設計

2010-03-16 16:34:06

Java編程語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 波多野结衣精品在线 | 亚洲天堂一区二区 | 天天摸天天干 | 国产精品国产成人国产三级 | 一区二区视频在线 | 国产精品成人一区二区三区 | 高清不卡毛片 | 亚洲第一天堂无码专区 | 翔田千里一区二区 | 91精品亚洲 | 韩日一区| 精品久久久久久亚洲精品 | 精品在线一区二区三区 | 国产真实精品久久二三区 | 日韩欧美国产精品一区 | 亚洲精品电影网在线观看 | 一区二区三区在线 | 欧美韩一区二区三区 | 日韩中文一区二区三区 | 国产精品久久久久久一级毛片 | 精品国产乱码久久久久久蜜退臀 | 国产精品美女久久久久久不卡 | 国产精品久久久久久久久久东京 | av资源中文在线天堂 | 亚洲一区二区三区在线播放 | 伊人精品久久久久77777 | 日本精品一区二区三区视频 | 欧美日韩视频 | 久久精品91久久久久久再现 | 欧美亚洲日本 | 99精品网 | 午夜成人免费视频 | 久久日韩精品一区二区三区 | 一区二区三区四区不卡视频 | 国产我和子的乱视频网站 | 国产久| 性高湖久久久久久久久 | 久久一热 | 97伦理电影网 | 免费看国产一级特黄aaaa大片 | 国产乱码精品一区二区三区中文 |