今天在實驗室摸魚逛部落格園,看到一個併發題目,考察條件變數的使用:
使用條件變數(AQS.Condition),實現兩個執行緒交替輸出從0到100,規定執行緒A先輸出,執行緒B後輸出,前者輸出偶數,後者輸出奇數
我的實現方法如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Solution {
private final static ReentrantLock lock = new ReentrantLock();
static int value = 0;
public static void main(String[] args) {
Condition l0 = lock.newCondition();
Condition l1 = lock.newCondition();
System.out.println("Tread A first print!");
Thread threadA = new Thread(() -> {
while (true) {
if (value >= 100) {
break;
}
try {
lock.lock();
while(value % 2 != 0) {
l0.await();
}
System.out.println("Thread A start to print —— " + value++);
l1.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread threadB = new Thread(() -> {
while (true) {
if (value >= 100) {
break;
}
try {
lock.lock();
while(value % 2 == 0) {
l1.await();
}
System.out.println("Thread B start to print —— " + value++);
l0.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadA.start();
threadB.start();
}
}
之前雖然把AQS、ReentrantLock的原始碼都看了好幾遍,但是真正自己動手寫多執行緒程式碼,還是有些生疏
值得注意的有三點:
- 使用條件變數之前,必須先獲取鎖。如果是ReentrantLock建立出來的條件變數,必須將條件變數的await和signal方法包裹在lock和unlock中使用。await方法會釋放獲取到的鎖,當等待的條件滿足時,會重新獲取鎖,這點不用擔心
- 如果要同步兩個執行緒,需要兩個條件變數,另外還需要外界的一個條件來幫助判斷,光靠兩個條件變數的await和signal會發生死鎖。這裡使用的條件是value是否為偶數。如果是生產者-消費者模型中,條件就應該是佇列是否為空。這些條件都應該放在while迴圈而不是if條件中,這是為了防止虛假喚醒
- 以後要多練練使用Idea除錯多執行緒程式碼,不容易但也不難