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

哲學家就餐問題:Java 多線程死鎖避免與 synchronized 實戰

     編輯:Java 相關知識

問題背景

「哲學家就餐問題」是經典的並發同步案例:5 位哲學家圍圓桌而坐,每人左右各有一根筷子,需同時拿到兩根筷子才能進餐。若所有哲學家同時拿起左手筷子,將陷入循環等待——死鎖。

三種經典解法

  1. 引入服務員 (信號量):限制同時進餐人數 ≤ 4,破壞「請求與保持」條件。
  2. 原子獲取雙筷子:檢查左右筷子均空閒時才一次性拿起,吃完同時歸還 (本文採用)。
  3. 按序號獲取資源:規定必須先拿編號較小的筷子,打破循環等待鏈。

Java 實現:方案二 (原子雙筷獲取)

核心類設計:

  • Philosopher 繼承 Thread,循環「思考 -> 獲取筷子 -> 進餐 -> 歸還筷子」。
  • Fork 管理 5 根筷子狀態 boolean[] used,提供 synchronized takeFork() 與 putFork(),利用 wait()/notifyAll() 協調。

完整代碼

package cn.edu.sdust.philosopher;

class Philosopher extends Thread {
    private final Fork fork;
    public Philosopher(String name, Fork fork) {
        super(name);
        this.fork = fork;
    }

    @Override
    public void run() {
        while (true) {
            thinking();
            fork.takeFork();      // 獲取左右兩根筷子
            eating();
            fork.putFork();       // 同時歸還
        }
    }

    private void eating() {
        System.out.println("I am Eating: " + getName());
        sleepQuietly(1000);
    }

    private void thinking() {
        System.out.println("I am Thinking: " + getName());
        sleepQuietly(1000);
    }

    private void sleepQuietly(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

class Fork {
    private final boolean[] used = new boolean[5]; // false = 空閒

    // 同時獲取左右筷子,否則等待
    public synchronized void takeFork() {
        int i = Integer.parseInt(Thread.currentThread().getName()); // 0~4
        while (used[i] || used[(i + 1) % 5]) {
            try { wait(); } catch (InterruptedException ignored) {}
        }
        used[i] = true;
        used[(i + 1) % 5] = true;
    }

    // 同時歸還並喚醒等待者
    public synchronized void putFork() {
        int i = Integer.parseInt(Thread.currentThread().getName());
        used[i] = false;
        used[(i + 1) % 5] = false;
        notifyAll();
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        Fork fork = new Fork();
        for (int i = 0; i < 5; i++) {
            new Philosopher(String.valueOf(i), fork).start();
        }
    }
}

運行結果分析

I am Thinking:0
I am Thinking:2
I am Thinking:3
I am Thinking:1
I am Thinking:4
I am Eating:0
I am Eating:2
I am Thinking:0
I am Eating:4
I am Thinking:2
I am Eating:1
I am Thinking:4
I am Eating:3
I am Thinking:1
I am Eating:0
I am Thinking:3
I am Eating:2
I am Thinking:0
I am Eating:4
I am Thinking:2
...

觀察可見:

  • 最多同時只有 2 人進餐 (如 0 與 2,或 4 與 1),且座位不相鄰。
  • 無死鎖發生,系統持續穩定運行。
  • synchronized 保證了檢查與設置的原子性;wait()/notifyAll() 實現了高效的線程協作。

關鍵技術點總結

  1. 鎖粒度:Fork 實例作為唯一鎖,保護整個 used[] 狀態,避免細粒度鎖導致的活鎖。
  2. 條件等待模式:while (條件) wait() 防止虛假喚醒。
  3. 資源釋放對稱性:獲取與歸還操作完全對稱,且在同一同步塊中完成,確保狀態一致性。
  4. 線程命名技巧:直接用哲學家編號 (0~4) 作為線程名,便於 Integer.parseInt 映射索引。

進階優化方向

  • 使用 ReentrantLock + Condition 替代 synchronized,支援公平鎖、可中斷等待。
  • 引入 Semaphore(4) 限制並發進餐數,簡化邏輯 (方案一)。
  • 採用無鎖算法 (CAS) 管理筷子狀態,進一步降低上下文切換開銷。

總結

哲學家就餐問題生動展示了死鎖產生條件與破解策略。通過「原子獲取多資源」配合 wait/notify 協作機制,Java 多線程程式可優雅解決資源競爭難題。掌握此模式,對理解數據庫連接池、線程池、分布式鎖等高級並發組件極有裨益。

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