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

Java 線程進階:同步機制、死鎖與飢餓問題及 JDK 1.2 改進

     編輯:Java 相關知識

在上一篇《Java 101 線程基礎》中,我們介紹了線程的基本概念與使用方式。本文將深入探討 Java 線程的缺陷與副作用,並分析 Sun JDK 1.2 如何改進線程運行機制。

同步機制

線程允許多個任務並發執行,並可共享對象與數據。然而,多線程同時存取同一數據會導致數據不一致。Java 提供 synchronized 關鍵字,可應用於方法或代碼塊,確保同一時間僅有一個線程執行該段代碼,其他線程將被阻塞直到釋放鎖。

定義同步方法的語法:

[public|private] synchronized {returnType} methodName(...) { ... }

同步代碼塊示例:

public class SomeClass {
    public void aMethod() {
        // 非同步代碼
        synchronized(this) {
            // 同步代碼塊
        }
        // 更多非同步代碼
    }
}

死鎖與飢餓

多線程編程中最常見的兩大問題是死鎖與飢餓。

死鎖

當兩個或多個線程互相持有對方需要的資源,導致彼此永遠等待,即發生死鎖。經典例子:你在 ATM 取款,卻提示「無現金」;運鈔車要加錢卻被你的車擋住;你不取到錢不走,運鈔車不加錢不走——雙方陷入永久等待。

死鎖發生的四個必要條件(缺一不可):

  • 互斥條件:資源一次只能被一個線程佔用。
  • 循環等待:線程 A 等 B,B 等 C,C 等 A,形成環路。
  • 部分分配:線程已獲得部分資源,仍請求其他資源。
  • 無搶佔條件:線程不釋放已持有資源,即使處於阻塞狀態。

破壞任一條件即可避免死鎖。例如:線程發現無法獲得第二資源時,主動釋放第一資源。

飢餓

系統未死鎖,但某些線程長期無法獲得 CPU 時間或資源,導致無法推進。飢餓無固定觸發規則,常見於優先級調度不當或資源分配策略缺陷。

線程高級用法

join() 方法

當主線程需等待子線程完成並獲取結果時,可使用 join():

CompleteCalcThread t = new CompleteCalcThread();
t.start();
// 做其他事...
t.join(); // 等待 t 結束
// 使用計算結果...

join() 有三種重載:無參無限等待、指定毫秒超時、指定毫秒+納秒超時。配合 isAlive() 可判斷線程是否仍在運行。

wait() / notify() 機制

實現生產者-消費者模式的核心。生產者向共享列表添加項目後調用 notify() 喚醒消費者;消費者調用 wait() 等待通知。注意:wait/notify 必須在 synchronized 方法或塊中調用。

偽代碼結構:

class ProdCons {
    class List {
        public synchronized boolean add(Object o) {...}
        public synchronized boolean remove(Object o) {...}
    }
    List data = new List();
    ProdThread producer;
    ConsThread consumer;
    ProdCons() {
        producer = new ProdThread(this);
        consumer = new ConsThread(this);
        producer.start();
        consumer.start();
    }
}

線程與 Sun JDK 1.2

早期線程實現存在缺陷,導致 stop()、suspend()、resume() 方法在 JDK 1.2 中被批評並最終棄用。主要問題:

  • 死鎖風險:Applet 切換頁面時,若生產者持有鎖被掛起,消費者將永久阻塞。
  • 數據不一致:線程被強制中止時,可能留下半更新的共享數據結構。

解決方案:自定義線程類,通過狀態標誌協作式暫停/停止,而非強制中斷。示例 MyThread 類:

public class MyThread extends Thread {
    static final int STATE_RUNNING = 0;
    static final int STATE_STOP = 1;
    static final int STATE_SUSPEND = 2;
    private int currentState = STATE_RUNNING;

    public final synchronized void changeState(int newState) {
        currentState = newState;
        if (STATE_RUNNING == currentState) notify();
    }

    private synchronized boolean checkState() {
        while (STATE_SUSPEND == currentState) {
            try { wait(); } catch (Exception e) {}
        }
        return STATE_STOP != currentState;
    }

    public void run() {
        do {
            if (!checkState()) return;
            // 執行實際工作
        } while (true);
    }
}

JDK 1.2 將線程安全責任轉移給開發者:需自行確保線程關閉時釋放資源、避免不一致、正常終止。

結語

線程功能強大但複雜。理解同步、死鎖、飢餓及協作式控制,能幫助開發者編寫更健壯的多線程應用。隨著 JDK 演進,掌握現代並發工具(如 java.util.concurrent)同樣重要。

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