首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 線程安全深度解析:從非安全示例到解決方案

     編輯:Java 相關知識

作為 Java Web 開發者,容器(如 Tomcat)已幫我們處理了大部分線程管理工作,但這不代表我們可以忽視線程安全的基本原理。許多開發者在 Java SE 基礎未紮實前就急著轉向 Java EE,導致知識體系「博而不精」。本文通過一個經典的線程不安全案例,剖析問題根源並提供多種解決思路。

線程不安全的直觀演示

定義一個帶有實例變數的計數器類:

public class Count {
    private int num; // 共享變數
    
    public void count() {
        for (int i = 1; i <= 10; i++) {
            num += i; // 非原子操作:讀取、計算、寫回
        }
        System.out.println(Thread.currentThread().getName() + "-" + num);
    }
}

啟動 10 個線程共享同一個 Count 實例:

public class ThreadTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            Count count = new Count(); // 所有線程共享同一實例
            public void run() {
                count.count();
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

執行結果分析

Thread-0-55
Thread-1-110
Thread-2-165
Thread-3-220
Thread-4-275
Thread-5-330
Thread-6-385
Thread-7-440
Thread-8-495
Thread-9-550

預期每個線程輸出 55(1+2+...+10),實際卻呈現遞增趨勢。原因在於 num += i 不是原子操作,多線程並發讀寫導致數據競爭,後啟動的線程讀取到已被修改的 num 值。

三種解決方案對比

方案一:將共享變數改為局部變數(推薦)

public class Count {
    public void count() {
        int num = 0; // 每次調用創建新的局部變數,線程隔離
        for (int i = 1; i <= 10; i++) {
            num += i;
        }
        System.out.println(Thread.currentThread().getName() + "-" + num);
    }
}

原理:局部變數存放在線程私有的棧幀中,天然線程安全,無同步開銷,效能最佳。

方案二:讓每個線程持有獨立實例

public class ThreadTest4 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            public void run() {
                Count count = new Count(); // 每個線程創建自己的實例
                count.count();
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }
}

原理:將共享狀態轉為線程封閉狀態,每個線程操作自己的對象副本,避免競爭。

方案三:每次啟動線程使用不同的 Runnable 實例(不推薦)

雖然可行,但會產生大量對象,增加 GC 壓力,且代碼冗餘。

核心原則總結

  1. 成員變數/靜態變數是線程不安全的來源:多線程共享堆內存中的同一份數據時,非原子操作會導致競態條件。
  2. 局部變數天然線程安全:存儲在線程私有棧中,不可被其他線程訪問。
  3. 實戰類比:Struts 1 Action 是單例,若定義成員變數會有線程安全隱患;Struts 2 每請求創建新 Action 實例,天然避免此問題。

延伸思考

當無法使用局部變數或線程封閉時,需引入同步機制:synchronized、Lock、ThreadLocal、原子類(AtomicInteger)或並發集合。選擇原則:優先無共享狀態設計,次之無鎖並發,最後才是鎖優化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved