Java 並發編程之同步隊列與等待隊列
在上一篇博客中,我簡單的介紹了對 Condition 和 ReentrantLock 的使用,但是想要更好的掌握多線程編程,單單會用是不夠的。這篇我會針對 Condition 方法中的 await 和 signal 的實現原理來梳理一下我的理解。
首先我們需要了解同步隊列和等待隊列的概念。簡單的理解是同步隊列存放着競爭同步資源的線程的引用(不是存放線程),而等待隊列存放着待喚醒的線程的引用。
同步隊列中存放着一個個節點,當線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構造成為一個節點並將其加入同步隊列,首節點表示的獲取同步狀態成功的線程節點。
Condition 維護着一個等待隊列與同步隊列相似。主要針對 await 和 signal 的操作。
現在不理解沒關系,下面用實例來認識一下它們在多線程中是如何使用的。這里實現了三個多線程的 run 方法。A 線程輸出 A 然后通知 B, 然后 B 通知 C。
public static class ThreadA extends Thread{
@Override
public void run(){
try{
lock.lock();
System.out.println("A進程輸出" + " : " + ++index);
conditionB.signal();
conditionA.await();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static class ThreadB extends Thread{
@Override
public void run(){
try{
lock.lock();
System.out.println("B進程輸出" + " : " + ++index);
conditionC.signal();
conditionB.await();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static class ThreadC extends Thread{
@Override
public void run(){
try{
lock.lock();
System.out.println("C進程輸出" + " : " + ++index);
conditionA.signal();
conditionC.await();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class CondtionTest {
public static ReentrantLock lock = new ReentrantLock();
public static Condition conditionA = lock.newCondition();
public static Condition conditionB = lock.newCondition();
public static Condition conditionC = lock.newCondition();
public static int index = 0;
public static void main(String[] args){
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
ThreadC threadC = new ThreadC();
threadA.start();//(1)
threadB.start();//(2)
threadC.start();//(3)
}
}
當 (1)(2)(3) 三個線程被調用時,因為三個線程同時競爭 lock,這里假設線程 A 拿到了 lock(線程 A 雖然是看起來是先 start(),但是正在的調用還是看調度程序的,所以這里只能假設是 A 線程拿到同步資源)。首節點表示的是正在操作同步資源的線程。所以現在的同步隊列是:
接着線程 A 輸出了:“A 進程輸出 : 1”。然后調用 conditionB.signal(),其實這一步的 signal 是沒什么意義的,因為 conditionB 現在沒有線程是可以被喚醒的。
當 conditionA.await() 被執行到的時候,線程 A 同步隊列中被移除,對應操作是鎖的釋放; 線程 A(節點 A) 接着被加入到 ConditionA 等待隊列,因為線程需要 singal 信號。
同步隊列:
A 等待隊列:
現在在同步隊列中的首節點是 B 節點,那么 B 線程占用了同步資源就可以開始運行了。先是輸出 “B 進程輸出 : 2”,同樣的 signal 操作也是沒有意義的,因為 conditionC 是沒有可以被喚醒的線程。當 conditionB.await() 被執行到的時候,線程 B 同步隊列中被移除,線程 B(節點 B)接着被加入到 ConditionB 等待隊列
同步隊列:
B 等待隊列:
終於輪到了 C 線程占用同步資源了,再輸出 “C 進程輸出:3” 之后,調用 conditionA.signal(),注意這個 signal 是有用的
因為在 conditionA 的等待隊列中 A 線程是在等待的,把它取出來加入到同步隊列中去競爭,但是這個時候線程 A 還沒喚醒。首節點還是 C。
同步隊列:
接着 conditionC.await() 被執行。線程 C 同步隊列中被移除,線程 C(節點 C) 接着被加入到 ConditionC 等待隊列
同步隊列:
C 等待隊列:
注意到同步隊列中的首節點已經變回了節點 A 了。所以線程 A 在剛剛等待的地方繼續執行,最后釋放了 lock。但是線程 B 和線程 C 最后也沒有其他線程去喚醒,狀態一直為 WAITING,而線程 A 的狀態為 TERMINATED