【多執行緒】常見問題簡單總結

大星說爬蟲發表於2023-12-29

多執行緒程式設計在提高程式效能方面非常有用,但也引入了一系列常見問題,主要包括競態條件、死鎖、執行緒飢餓和活鎖等。以下是這些問題的解釋以及如何在Java中解決它們的例子。


1. 競態條件(Race Condition)

競態條件發生在兩個或多個執行緒訪問共享資源並嘗試同時修改它時。這可能導致不一致和不可預測的結果。


場景:

共享資源: 當多個執行緒訪問和修改同一個變數或資源,而沒有適當的同步措施時。

非原子操作: 操作如遞增一個計數器,這需要讀取、修改和寫入值,這些步驟在沒有同步的情況下會被中斷。

先檢查後執行: 先檢查資源狀態,然後根據狀態執行操作的模式,如果狀態在檢查和執行之間被另一個執行緒改變,會導致問題。

解決方法:

使用同步機制,如synchronized關鍵字或顯式鎖(如ReentrantLock),來確保一次只有一個執行緒可以訪問共享資源。


Java 示例


public class Counter {

    private int count = 0;

 

    public synchronized void increment() {

        count++;

    }

 

    public synchronized int getCount() {

        return count;

    }

}

2. 死鎖(Deadlock)

死鎖是指兩個或多個執行緒永遠等待對方釋放鎖的情況。這通常發生在每個執行緒都持有一個鎖並嘗試獲取其他執行緒已持有的鎖時。


場景:

-互斥條件: 程式中的多個執行緒需要同時鎖定多個資源。


請求和保持條件: 執行緒已經持有至少一個資源,並且正在等待獲取額外的資源,這些資源可能被其他已經鎖定了它們的執行緒持有。

不剝奪條件: 資源被執行緒持有,直到自願釋放,不能被強制剝奪。

迴圈等待條件: 發生在一組執行緒中,每個執行緒都在等待下一個執行緒所持有的資源。

解決方法:

避免巢狀鎖,使用定時鎖(嘗試鎖),或者以一致的順序獲取鎖。


Java 示例


public class Account {

    private int balance = 10000;

 

    // Transfer method with ordered locks to avoid deadlock

    public void transfer(Account from, Account to, int amount) {

        synchronized (from) { // First lock

            synchronized (to) { // Second lock

                if (from.balance >= amount) {

                    from.balance -= amount;

                    to.balance += amount;

                }

            }

        }

    }

}

3. 執行緒飢餓(Thread Starvation)

執行緒飢餓發生在低優先順序的執行緒長時間得不到執行,因為高優先順序的執行緒一直佔用CPU資源。


場景:

-優先順序不當: 當一個高優先順序的執行緒不斷地被排程,而低優先順序的執行緒得不到足夠的CPU時間。

-鎖的不當使用: 長時間持有鎖,特別是在執行耗時操作時,可能會導致其他執行緒長時間等待。

-執行緒數量過多: 當執行緒的數量遠遠超過處理器的數量,導致某些執行緒很少獲得CPU時間。


解決方法:

使用公平鎖(如ReentrantLock的公平模式),或者調整執行緒優先順序,確保低優先順序執行緒也能獲得執行時間。


Java 示例


import java.util.concurrent.locks.ReentrantLock;

 

public class FairLockExample {

    private final ReentrantLock lock = new ReentrantLock(true); // Fair lock

 

    public void fairMethod() {

        lock.lock();

        try {

            // Critical section code

        } finally {

            lock.unlock();

        }

    }

}

4. 活鎖(Livelock)

活鎖是指執行緒雖然沒有被阻塞,但也無法向前推進因為不斷重複相同的操作,通常是因為執行緒間的相互響應。


場景:

錯誤的失敗恢復策略: 當執行緒嘗試執行一個操作失敗後,它會嘗試重試相同的操作,而這個操作由於某些外部條件總是失敗。

過度響應: 當兩個執行緒或更多執行緒設計為響應對方的動作時,它們可能會陷入一個迴圈,其中每個執行緒都在嘗試避免與其他執行緒發生衝突。

解決方法:

引入隨機性,例如在重試之前等待隨機的時間,或者改變重試的策略。


Java 示例


public class LivelockExample {

    private boolean isActive;

 

    public synchronized void attemptAction() {

        while (isActive) {

            // 執行緒在這裡嘗試某個操作,但失敗了

            try {

                Thread.sleep((long) (Math.random() * 100)); // 隨機等待

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            }

            // 一些其他的邏輯,可能會改變isActive的狀態

        }

    }

 

    public synchronized void setActive(boolean active) {

        isActive = active;

    }

}

在多執行緒程式設計時,始終要確保對共享資源的訪問是適當同步的,同時要留意程式碼中可能導致死鎖或活鎖的設計。還應該避免對執行緒優先順序的依賴,因為這可能會在不同的平臺上導致不同的行為。


在設計多執行緒程式時,理解這些問題及其出現的場景是非常重要的。這有助於程式設計師採取預防措施,比如使用適當的同步機制、設計合理的執行緒優先順序和鎖策略,以及實現健壯的錯誤處理和恢復策略。透過這些措施,可以大大減少多執行緒應用程式中出現問題的可能性。



來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70030722/viewspace-3002107/,如需轉載,請註明出處,否則將追究法律責任。

相關文章