LeetCode 題目描述
我們提供一個類:
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
兩個不同的線程將會共用一個 FooBar 實例。其中一個線程將會調用 foo() 方法,另一個線程將會調用 bar() 方法。
請設計修改程序,以確保 “foobar” 被輸出 n 次。
示例 1:
輸入: n = 1
輸出: "foobar"
解釋: 這裏有兩個線程被異步啓動。其中一個調用 foo() 方法, 另一個調用 bar() 方法,"foobar" 將被輸出一次。
示例 2:
輸入: n = 2
輸出: "foobarfoobar"
解釋: "foobar" 將被輸出兩次。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/print-foobar-alternately
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
題解
解題思路爲:
- 多線程併發訪問同一個對象的成員方法
- 成員方法的執行順序是固定的(突破點)
對於多線程固定順序的執行,我們可以採用 Thread.join 方法。但此處不適用,此處我們只可以修改方法內部實現機制,並不能控制線程執行的方式,所以應該是線程等待喚醒機制。
順序執行實現方案:
- 方法內部使用布爾變量控制當前方法是否可以執行,即 if(boolean)` 範式,某個方法執行後修改布爾值達到順序控制
- 使用併發工具類 Semaphore 實現(初始許可爲 0,執行前置條件後釋放一個許可)(感興趣可嘗試實現)
此處如果使用
CyclicBarrier
工具類不能保證 bar 在 foo 前執行,需要額外控制。
方法內部使用布爾變量控制實現
對於 synchronized 關鍵字,該實現添加在方法簽名上而不是 for 循環內部,減少鎖的操作,而是在獲取鎖後使用等待通知機制喚醒後繼續執行。可參考博文-Java 線程等待通知機制(wait、notify)
class FooBar {
private volatile boolean tmp = false;
private int n;
public FooBar(int n) {
this.n = n;
}
public synchronized void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
if (tmp) {
wait();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
tmp = true;
notify();
}
}
public synchronized void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
if (!tmp) {
wait();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
tmp = false;
notify();
}
}
}
參考
有關併發編程相關知識參考本博客專欄對應文章。
- 本題在 LeetCode-1114. 按序打印(多線程) 順序執行基礎上增加了 for 循環內部等待通知操作。