首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java 靜態變量累加陷阱:s1+s2 值為何是 15?static 關鍵字內存模型圖解

     編輯:程式設計綜合問答

Java 靜態變量(static)是初學者面試高頻考點、項目中常見 Bug 源頭。本文以「s1+s2 值為何是 15」為切入點,從字節碼層面、內存模型、線程安全三個維度徹底講透 static 變量共享機制與累加陷阱。

現象代碼還原

public class StaticTest {
    static int num = 0;  // 靜態變量,屬於類,不屬於實例
    
    public StaticTest() {
        num += 5;  // 每次 new 實例,num +5
    }
    
    public static void main(String[] args) {
        StaticTest s1 = new StaticTest();  // num = 5
        StaticTest s2 = new StaticTest();  // num = 10
        System.out.println("s1.num = " + s1.num);  // 10
        System.out.println("s2.num = " + s2.num);  // 10
        System.out.println("s1 + s2 = " + (s1.num + s2.num));  // 20
    }
}

若輸出 s1+s2 = 15,說明構造函數執行次數或累加邏輯與預期不符,需檢查實例創建次數。

核心知識點:static 變量屬於類,全進程共享一份

內存模型圖解

方法區 / Metaspace (JDK 8+)          堆
┌─────────────────────────────┐      ┌──────────────────┐
│ StaticTest.class            │      │ s1 實例          │
│ ├─ 方法表                   │      │ ├─ 非靜態字段... │
│ ├─ 靜態字段: num = 10  ◀────┼──────┤ (無 num 副本)   │
│ └─ 常量池                   │      └──────────────────┘
└─────────────────────────────┘      ┌──────────────────┐
                                     │ s2 實例          │
                                     │ ├─ 非靜態字段... │
                                     │ (無 num 副本)    │
                                     └──────────────────┘
  • static int num 存儲在方法區(JDK 8 之前 PermGen,JDK 8+ Metaspace),僅有一份。
  • 所有實例 s1、s2、s3... 訪問 num 時,**指向同一個內存地址**。
  • s1.num 和 s2.num 在編譯期會被編譯器優化為 StaticTest.num(字節碼 getstatic 指令)。

字節碼層面驗證

// javap -c StaticTest.class 關鍵片段
  // 構造函數
   0: aload_0
   1: invokespecial #1  // Object.
   4: getstatic     #2  // Field num:I  ← 讀取類靜態變量
   7: iconst_5
   8: iadd
   9: putstatic     #2  // Field num:I  ← 寫回類靜態變量
  12: return

  // main 中 s1.num + s2.num
  10: getstatic     #2  // Field num:I  ← 讀取 num (10)
  13: getstatic     #2  // Field num:I  ← 再次讀取 num (10)
  16: iadd
  17: invokevirtual #3  // PrintStream.println

兩次 getstatic 讀取的是**同一個靜態槽位**,值均為 10,相加得 20。

為什麼會出現 15?可能的代碼變體

static 變量常見陷阱與避坑指南

代碼變體執行流程結果
構造函數 num += 5,創建 3 個實例5 → 10 → 15s1.num=15, s2.num=15, s3.num=15, 和=30
構造函數 num++,創建 3 次 + 額外手動 num+=101→2→3→13視具體調用點而定
num 非 static,但錯誤用 s1.num + s2.num各實例獨立累加s1=5, s2=5, 和=10
混合 static/非 static 同名字段(字段遮蔽)極易混淆不可預測,嚴禁同名
陷阱錯誤代碼後果修正
多線程非原子累加static int count; count++;數據丟失,結果小於預期AtomicInteger / synchronized / LongAdder
靜態集合共享導致污染static List list = new ArrayList();不同業務流程互相影響改為局部變量、ThreadLocal、或明確清理
單例模式雙重檢查缺少 volatilestatic Singleton instance;指令重排導致部分初始化對象泄露static volatile Singleton instance;
類加載器隔離導致多份副本Tomcat/WebLogic 多應用共用 JAR每個 Webapp 類加載器各有一份 static放入共享類加載器路徑、或用 JNDI/Redis 共享
序列化不序列化 staticstatic transient int x;反序列化後 static 值為類當前值,非序列化時值理解 static 不參與序列化,反序列化後不恢復

線程安全累加器選型指南

// 1. 低併發、簡單計數
static AtomicInteger count = new AtomicInteger();
count.incrementAndGet();

// 2. 高併發、統計計數 (JDK 8+)
static LongAdder adder = new LongAdder();
adder.increment();  // 無鎖分段累加,極高吞吐
long value = adder.sum();

// 3. 需要原子複合操作 (CAS 循環)
static AtomicLong seq = new AtomicLong();
seq.updateAndGet(x -> x + 1);

// 4. 只是單線程或已有鎖保護
static long counter = 0L;  // 非 static 更好

總結

static 變量屬於類、存儲在方法區、全進程(同類加載器)共享一份。構造函數中 num += 5 每次實例化都修改同一個 num,最終所有實例讀取到的值相同。s1.num + s2.num 實際是 StaticTest.num + StaticTest.num,結果為 2 * 最终num值。理解「靜態歸類、實例歸對象」,靜態累加陷阱自然迎刃而解。

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