線程池飢餓問題


🤜 示例

例如,海底撈的服務員(線程),輪流處理每位客人的點餐(任務),如果為每位客人都配一名專屬的服務員,那
么成本就太高了(對比另一種多線程設計模式:Thread-Per-Message)

注意,不同任務類型應該使用不同的線程池,這樣能夠避免飢餓,並能提升效率

例如,如果一個餐館的工人既要招呼客人(任務類型A),又要到后廚做菜(任務類型B)顯然效率不咋地,分成
服務員(線程池A)與廚師(線程池B)更為合理,當然你能想到更細致的分工

演示:固定大小線程池會有飢餓現象

兩個工人是同一個線程池中的兩個線程

他們要做的事情是:為客人點餐和到后廚做菜,這是兩個階段的工作

客人點餐:必須先點完餐,等菜做好,上菜,在此期間處理點餐的工人必須等待

后廚做菜:沒啥說的,做就是了

比如工人A 處理了點餐任務,接下來它要等着 工人B 把菜做好,然后上菜,他倆也配合的蠻好
但現在同時來了兩個客人,這個時候工人A 和工人B 都去處理點餐了,這時沒人做飯了,導致飢餓

@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {

    static final List<String> MENU = Arrays.asList("地三鮮", "宮保雞丁", "辣子雞丁", "烤雞翅");
    static Random RANDOM = new Random();
    // 隨機返回一個菜
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    
    public static void main(String[] args) {
        //固定大小為2的線程池
        ExecutorService waiterPool = Executors.newFixedThreadPool(2);

        waiterPool.execute(() -> {
            log.debug("處理點餐...");
            
            //具體的做菜交與另一線程去做
            Future<String> f = waiterPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(() -> {
            log.debug("處理點餐...");
            Future<String> f = waiterPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

輸出

18:30:48.374 c.TestDeadLock [pool-1-thread-2] - 處理點餐...
18:30:48.374 c.TestDeadLock [pool-1-thread-1] - 處理點餐...

發生了飢餓,這不叫死鎖,如果使用jstack,這與死鎖的定義也不相同。

👨‍🚒 解決方法

可以增加線程池的大小,不過不是根本解決方案,還是前面提到的,不同的任務類型,采用不同的線程
池,例如:

@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {

    static final List<String> MENU = Arrays.asList("地三鮮", "宮保雞丁", "辣子雞丁", "烤雞翅");
    static Random RANDOM = new Random();
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    public static void main(String[] args) {
        //兩個線程池
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService cookPool = Executors.newFixedThreadPool(1);

        waiterPool.execute(() -> {
            log.debug("處理點餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(() -> {
            log.debug("處理點餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

輸出:

18:31:52.854 c.TestDeadLock [pool-1-thread-1] - 處理點餐...
18:31:52.858 c.TestDeadLock [pool-2-thread-1] - 做菜
18:31:52.858 c.TestDeadLock [pool-1-thread-1] - 上菜: 辣子雞丁
18:31:52.859 c.TestDeadLock [pool-1-thread-1] - 處理點餐...
18:31:52.860 c.TestDeadLock [pool-2-thread-1] - 做菜
18:31:52.860 c.TestDeadLock [pool-1-thread-1] - 上菜: 地三鮮

可以發現,現在正常了。


小結:

當線程池任務間發生了依賴的時候,就應該考慮線程池飢餓的問題。

線程池中的任務應是同類的、獨立的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM