在《操作系統同步原語》 這篇文章中,介紹了操作系統在面對 進程/線程 間同步的時候,所支持的一些同步原語,其中 semaphore 信號量 和 mutex 互斥量是最重要的同步原語。
在使用基本的 mutex 進行併發控制時,需要程序員非常小心地控制 mutex 的 down 和 up 操作,否則很容易引起死鎖等問題。爲了更容易地編寫出正確的併發程序,所以在 mutex 和 semaphore 的基礎上,提出了更高層次的同步原語 monitor,管程就可以對開發者屏蔽掉這些手動細節,在語言內部實現,更加簡單易用。
不過需要注意的是,操作系統本身並不支持 monitor 機制,實際上,monitor 是屬於編程語言的範疇,當你想要使用 monitor 時,先了解一下語言本身是否支持 monitor 原語,例如 C 語言它就不支持 monitor,Java 語言支持 monitor。
一般的 monitor 實現模式是編程語言在語法上提供語法糖,而如何實現 monitor 機制,則屬於編譯器的工作。
JAVA管程
在Java使用是的是Mesa 梅莎
管程模型。其結構如下圖所示
- 多個線程進入管程的入口隊列e(_EntryList),並試圖獲取臨界區鎖。獲取到鎖的線程進入臨界區,其他線程仍然在e中。
- 如果在業務代碼裏調用Object鎖對象的wait()方法(底層則是調用ObjectMonitor(java管程)的wait原語),該線程阻塞,釋放臨界區的鎖,離開臨界區並進入q(_WaitSet)。
- 臨界區執行完畢後調用notify原語(相當於signal),喚醒q中的一個線程。執行完畢的線程釋放鎖,並離開管程的作用域。
- 被喚醒的線程進入隊列e,返回第1步重新開始獲取鎖。
Meas管程的特點
線程阻塞狀態被喚醒之後不會立即執行,而是回到入口等待。當阻塞的線程再次獲取到CPU後,會執行wait()方法後的代碼。這帶來的潛在問題是線程獲取到CPU後,原先滿足的條件可能已經不再滿足,必須重新檢查。所以在Mesa管程模型下編寫程序時,檢查條件應該用while,而不是if:
while (!condition) {
wait(a)
}
下面是從網上找到的其他人寫的驗證檢查條件應該用while的代碼
package com.test;
import java.util.ArrayList;
import java.util.List;
public class EarlyNotify extends Object {
private List<String> list = new ArrayList<String>();
public String removeItem() throws InterruptedException {
print("in removeItem() - entering");
synchronized (list) {
if (list.isEmpty()) { // 這裏用if語句會發生危險
print("in removeItem() - about to wait()");
list.wait();
print("in removeItem() - done with wait()");
}
// 刪除元素
String item = (String) list.remove(0);
print("in removeItem() - leaving");
return item;
}
}
public void addItem(String item) {
print("in addItem() - entering");
synchronized (list) {
// 添加元素
list.add(item);
print("in addItem() - just added: '" + item + "'");
// 添加後,通知所有線程
list.notifyAll();
print("in addItem() - just notified");
}
print("in addItem() - leaving");
}
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final EarlyNotify en = new EarlyNotify();
Runnable consumer = new Runnable() {
public void run() {
try {
String item = en.removeItem();
print("in run() - returned: '" + item + "'");
} catch (InterruptedException ix) {
print("interrupted!");
} catch (Exception x) {
print("threw an Exception!!!\n" + x);
}
}
};
Runnable producer = new Runnable() {
public void run() {
en.addItem("Hello!");
}
};
try {
// 啓動第一個刪除元素的線程
Thread threadConsumer1 = new Thread(consumer, "threadConsumer1");
threadConsumer1.start();
Thread.sleep(500);
// 啓動第二個刪除元素的線程
Thread threadConsumer2 = new Thread(consumer, "threadConsumer2");
threadConsumer2.start();
Thread.sleep(500);
// 啓動增加元素的線程
Thread threadProducer = new Thread(producer, "threadProducer");
threadProducer.start();
// Thread.sleep(10000); // wait 10 seconds
// threadA1.interrupt();
// threadA2.interrupt();
} catch (InterruptedException x) {
}
}
}
運行結果
threadConsumer1: in removeItem() - entering
threadConsumer1: in removeItem() - about to wait()
threadConsumer2: in removeItem() - entering
threadConsumer2: in removeItem() - about to wait()
threadProducer: in addItem() - entering
threadProducer: in addItem() - just added: 'Hello!'
threadProducer: in addItem() - just notified
threadConsumer2: in removeItem() - done with wait()
threadConsumer2: in removeItem() - leaving
threadProducer: in addItem() - leaving
threadConsumer1: in removeItem() - done with wait()
threadConsumer2: in run() - returned: 'Hello!'
threadConsumer1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
PS:Hoare管程模型在線程被喚醒後就會立即切換上下文,讓被喚醒的線程先執行,但會觸發更多的上下文切換操作,浪費CPU時間。
ObjectMonitor
//構造方法
ObjectMonitor() {
_header = NULL;
//獲取管程鎖的次數
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
//持有該ObjectMonitor線程的指針
_owner = NULL;
//管程的條件變量的資源等待隊列
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
//管程的入口線程隊列
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
//所有阻塞的線程都會被封裝成ObjectWaiter對象
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
jlong _notifier_tid;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};