java的synchronized鎖實現與Monitor(管程)機制

        在《操作系統同步原語》 這篇文章中,介紹了操作系統在面對 進程/線程 間同步的時候,所支持的一些同步原語,其中 semaphore 信號量 和 mutex 互斥量是最重要的同步原語。
        在使用基本的 mutex 進行併發控制時,需要程序員非常小心地控制 mutex 的 down 和 up 操作,否則很容易引起死鎖等問題。爲了更容易地編寫出正確的併發程序,所以在 mutex 和 semaphore 的基礎上,提出了更高層次的同步原語 monitor,管程就可以對開發者屏蔽掉這些手動細節,在語言內部實現,更加簡單易用。

        不過需要注意的是,操作系統本身並不支持 monitor 機制,實際上,monitor 是屬於編程語言的範疇,當你想要使用 monitor 時,先了解一下語言本身是否支持 monitor 原語,例如 C 語言它就不支持 monitor,Java 語言支持 monitor。
        一般的 monitor 實現模式是編程語言在語法上提供語法糖,而如何實現 monitor 機制,則屬於編譯器的工作

JAVA管程

        在Java使用是的是Mesa 梅莎管程模型。其結構如下圖所示

  1. 多個線程進入管程的入口隊列e(_EntryList),並試圖獲取臨界區鎖。獲取到鎖的線程進入臨界區,其他線程仍然在e中。
  2. 如果在業務代碼裏調用Object鎖對象的wait()方法(底層則是調用ObjectMonitor(java管程)的wait原語),該線程阻塞,釋放臨界區的鎖,離開臨界區並進入q(_WaitSet)。
  3. 臨界區執行完畢後調用notify原語(相當於signal),喚醒q中的一個線程。執行完畢的線程釋放鎖,並離開管程的作用域。
  4. 被喚醒的線程進入隊列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.hpp源碼...

  //構造方法
  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);
};

參考說一說管程(Monitor)及其在Java synchronized機制中的體現

JAVA併發(2)—PV機制與monitor(管程)機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章