Java並行(十二)----執行緒應用之多執行緒解決燒水泡茶問題

2023-06-28 06:01:16

1、背景

統籌方法,是一種安排工作程序的數學方法。它的實用範圍極廣泛,在企業管理和基本建設中,以及關係複雜的科研專案的組織與管理中,都可以應用。

怎樣應用呢?主要是把工序安排好。

比如,想泡壺茶喝。當時的情況是:開水沒有;水壺要洗,茶壺、茶杯要洗;火已生了,茶葉也有了。怎麼辦?

  • 辦法甲:洗好水壺,灌上涼水,放在火上;在等待水開的時間裡,洗茶壺、洗茶杯、拿茶葉;等水開了,泡茶喝。

  • 辦法乙:先做好一些準備工作,洗水壺,洗茶壺茶杯,拿茶葉;一切就緒,灌水燒水;坐待水開了,泡茶喝。

  • 辦法丙:洗淨水壺,灌上涼水,放在火上,坐待水開;水開了之後,急急忙忙找茶葉,洗茶壺茶杯,泡茶喝。

哪一種辦法省時間?我們能一眼看出,第一種辦法好,後兩種辦法效率較低。

水壺不洗,不能燒開水,因而洗水壺是燒開水的前提。沒開水、沒茶葉、不洗茶壺茶杯,就不能泡茶,因而這些又是泡茶的前提。它們的相互關係,可以用下邊的箭頭圖來表示:

從這個圖上可以一眼看出,辦法甲總共要16分鐘(而辦法乙、丙需要20分鐘)。如果要縮短工時、提高工作效率,應當主要抓燒開水這個環節,而不是抓拿茶葉等環節。同時,洗茶壺茶杯、拿茶葉總共不過4分鐘,大可利用「等水開」的時間來做。

洗茶壺,洗茶杯,拿茶葉,或先或後,關係不大,而且同是一個人的活兒,因而可以合併成為:

 

2、思路

參考上圖,用兩個執行緒(兩個人共同作業)模擬燒水泡茶過程

文中辦法乙、丙都相當於任務序列

而圖一相當於啟動了 4 個執行緒,有點浪費

用 sleep(n) 模擬洗茶壺、洗水壺等耗費的時間

3、解法1:join

@Slf4j(topic = "c.Test16")
public class Test16 {
​
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("洗水壺");
            // TimeUnit.SECONDS.sleep(i); 
            sleep(1);  // sleep 1s  可使用上方的程式碼睡眠
            log.debug("燒開水");
            sleep(5); // sleep 5s
        },"老王");
​
        Thread t2 = new Thread(() -> {
            log.debug("洗茶壺");
            sleep(1); // sleep 1s
            log.debug("洗茶杯");
            sleep(2); // sleep 2s
            log.debug("拿茶葉");
            sleep(1); // sleep 1s
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("泡茶");
        },"小王");
​
        t1.start();
        t2.start();
    }
}

輸出

解法1 的缺陷:

  • 上面模擬的是小王等老王的水燒開了,小王泡茶,如果反過來要實現老王等小王的茶葉拿來了,老王泡茶呢?程式碼最好能適應兩種情況

  • 上面的兩個執行緒其實是各執行各的,如果要模擬老王把水壺交給小王泡茶,或模擬小王把茶葉交給老王泡茶呢

4、解法2:wait/notify

class S2 {
    static String kettle = "冷水";
    static String tea = null;
    static final Object lock = new Object();
    static boolean maked = false;
​
    public static void makeTea() {
        new Thread(() -> {
            log.debug("洗水壺");
            sleep(1);
            log.debug("燒開水");
            sleep(5);
            synchronized (lock) {
                kettle = "開水";
                lock.notifyAll();
                while (tea == null) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (!maked) {
                    log.debug("拿({})泡({})", kettle, tea);
                    maked = true;
                }
            }
        }, "老王").start();
​
        new Thread(() -> {
            log.debug("洗茶壺");
            sleep(1);
            log.debug("洗茶杯");
            sleep(2);
            log.debug("拿茶葉");
            sleep(1);
            synchronized (lock) {
                tea = "花茶";
                lock.notifyAll();
                while (kettle.equals("冷水")) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (!maked) {
                    log.debug("拿({})泡({})", kettle, tea);
                    maked = true;
                }
            }
        }, "小王").start();
    }
}

輸出

20:04:48.179 c.S2 [小王] - 洗茶壺
20:04:48.179 c.S2 [老王] - 洗水壺
20:04:49.185 c.S2 [老王] - 燒開水
20:04:49.185 c.S2 [小王] - 洗茶杯
20:04:51.185 c.S2 [小王] - 拿茶葉
20:04:54.185 c.S2 [老王] - 拿(開水)泡(花茶)

解法2 解決了解法1 的問題,不過老王和小王需要相互等待,不如他們只負責各自的任務,泡茶交給第三人來做

5、解法3:第三者協調

class S3 {
    static String kettle = "冷水";
    static String tea = null;
    static final Object lock = new Object();
​
    public static void makeTea() {
        new Thread(() -> {
            log.debug("洗水壺");
            sleep(1);
            log.debug("燒開水");
            sleep(5);
            synchronized (lock) {
                kettle = "開水";
                lock.notifyAll();
            }
        }, "老王").start();
​
        new Thread(() -> {
            log.debug("洗茶壺");
            sleep(1);
            log.debug("洗茶杯");
            sleep(2);
            log.debug("拿茶葉");
            sleep(1);
            synchronized (lock) {
                tea = "花茶";
                lock.notifyAll();
            }
        }, "小王").start();
​
        new Thread(() -> {
            synchronized (lock) {
                while (kettle.equals("冷水") || tea == null) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("拿({})泡({})", kettle, tea);
            }
        }, "王夫人").start();
​
    }
}

輸出

20:13:18.202 c.S3 [小王] - 洗茶壺
20:13:18.202 c.S3 [老王] - 洗水壺
20:13:19.206 c.S3 [小王] - 洗茶杯
20:13:19.206 c.S3 [老王] - 燒開水
20:13:21.206 c.S3 [小王] - 拿茶葉
20:13:24.207 c.S3 [王夫人] - 拿(開水)泡(花茶)