" />

亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

探索 Java 同步機制

系統(tǒng) 2110 0
本文從典型的 Monitor Object 設(shè)計模式入手,從一個新的視角,來探討 Java 語言的同步機制。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

本文將從兩個方面進行闡述:

  1. 使 用 C++ 語言來描述 Monitor Object 設(shè)計模式。Java 對于這樣一個典型的模式做了很好的語言層面的封裝,因此對于 Java 的開發(fā)者來說,很多關(guān)于該模式本身的東西被屏蔽掉了。本文試圖使用 Native C++ 語言,幫助讀者從本質(zhì)上對 Monitor object 設(shè)計模式有一個更全面的認識。
  2. 結(jié)合 C++ 版本的 Monitor Object 設(shè)計模式,引領(lǐng)讀者對于 Java 同步機制有一個更深刻的認識,幫助讀者正確有效地使用 Java 同步機制。

預(yù)備知識

在開始正式討論之前,需要了解一些預(yù)備知識。

什么是 RAII

資 源獲取即初始化(RAII, Resource Acquisition Is Initialization)是指,在一個對象的構(gòu)造函數(shù)中獲得資源 , 并且在該對象的析構(gòu)函數(shù)中釋放它。這個資源可以是對象、內(nèi)存、文件句柄或者其它類型。實現(xiàn)這種功能的類,我們就說它采用了資源獲取即初始化(RAII)的 方式。 RAII 是一種很典型的語言慣用法,被很多的 OO 語言所使用,下面是 C++ 的例子。


清單 1. RAII Using C++
            
class Raii {
public:
// Store a pointer to the resource and initialize the resource.
Raii(Resource &resource)
:m_pRes (&resource){
m_pRes->initialize ();
}
// Close the resource when the execution goes out of scope.
virtual ~Raii() {
m_pRes->close ();
}
private:
// Pointer to the resource we're managing.
Resource *m_pRes;
// ... maybe need disallow copying and assignment ...
};

使 用 RAII 的好處是:由于析構(gòu)函數(shù)由系統(tǒng)自動調(diào)用,這樣可以幫助我們自動地隱式釋放我們所獲取的資源。事情上,我們熟知的很多 c++ 技術(shù)都用到了這一設(shè)計模式,比如:智能指針 (Smart Pointer),以及我們接下來要討論的范圍鎖 (Scoped Lock) 。

不同于 C++,Java 對象沒有析構(gòu)函數(shù),Java System 提供了 GC 來管理內(nèi)存資源。而對于像數(shù)據(jù)庫連接,Sockets 這樣類型的資源, Java 提供了 finalize() 來處理。但是,請注意,Java 的 finalizer 與 C++ 的析構(gòu)函數(shù)是不同的, finalize() 函數(shù)由 GC 異步地在某個恰當?shù)臅r候調(diào)用,我們不能等同地使用 finalize() 來實現(xiàn) C++ 里的 RAII 。通常的做法是使用 Java 提供的 finally 語句塊。


清單 2. RAII Using Java
            
MyResource res = null;
try {
res = new MyResource();
// Use the resource
} finally {
//At exit point, close the resource.
if (res != null) { res.close(); }
}

什么是區(qū)域鎖 (Scoped Lock)

區(qū)域鎖是指線程執(zhí)行進入一個區(qū)域時,一個鎖將自動被獲取,當該線程執(zhí)行離開這個區(qū)域時,這個鎖將被自動釋放。 C++ 區(qū)域鎖的實現(xiàn)使用了 RAII 技術(shù) , 實現(xiàn)如下。


清單 3. Scoped Lock Using C++
            
template <class LOCK>
class Guard {
public:
// Store a pointer to the lock and acquire the lock.
Guard (LOCK &lock)
:m_pLlock (&lock), m_bOwner (false) {
m_pLlock->acquire ();
m_bOwner = true;
}
// Release the lock when the guard goes out of scope,
// but only if <acquire> succeeded.
virtual ~Guard () {
if (m_bOwner) m_pLlock->release ();
}
private:
// Pointer to the lock we're managing.
LOCK *m_pLlock;
// Records if the lock is held by this object.
bool m_bOwner;
// ... maybe need disallow copying and assignment ...
};

Guard 是一個模板類,LOCK 類型指的是對操作系統(tǒng)提供的線程鎖的抽象,比如,在 Windows 平臺上,LOCK 可以是對 CRITICAL_SECTION 的封裝。

那么對于 Java,怎么實現(xiàn)區(qū)域鎖呢?不必擔心,Java 對于區(qū)域鎖模式在語言層面上已經(jīng)做了封裝,所以對于 Java 開發(fā)者來說,不必像 C++ 這樣來開發(fā)自己的區(qū)域鎖類,這就是我們所熟知的 synchronized 關(guān)鍵字。


清單 4. Scoped Lock Using Java
            
public int scopedLockSample() {
synchronized(this) {
try {
//do some work…
} catch( MyException1 e) {
//no need release lock explicitly
return -1;
} catch( MyException2 e) {
//no need release lock explicitly
return -2;
}
//other exceptions handling...
}
return 0;
}

synchronized 保證在進入該區(qū)域后,獲得對象鎖,不管最終該函數(shù)從哪里退出,該對象鎖都會被正確釋放。

什么是條件變量 (Condition Variables)

條 件變量通常被一個線程用于使自己等待,直到一個涉及共享數(shù)據(jù)的條件表達式到達特定的狀態(tài)。當另外的協(xié)作線程指示共享數(shù)據(jù)的狀態(tài)已發(fā)生變化,調(diào)度器就喚醒在 該條件變量上掛起的線程。于是新喚醒的線程重新對它的條件表達式進行求值,如果共享數(shù)據(jù)已到達合適狀態(tài),就恢復(fù)處理。以下是條件變量的 C++ 實現(xiàn)。


清單 5. Thread Condition Using C++
            
class Thread_Condition {
public:
// Initialize the condition variable and associate it with the specified lock.
Thread_Condition (const Thread_Mutex &m)
:m_obMutex(m) {
cond_init (&cond_, USYNC_THREAD, 0);
}
// Destroy the condition variable.
virtual ~Thread_Condition () {
cond_destroy (&cond_);
}
// Wait for the <Thread_Condition> to be notified
// or until <timeout> has elapsed. If <timeout> == 0 then wait indefinitely.
void wait (Time_Value *timeout = 0) {
cond_timedwait(&cond_, &m_obMutex.m_lock,timeout == 0?0:timeout->msec ());
}
// Notify one thread waiting on <Thread_Condition>.
void notify () { cond_signal (&cond_); }
// Notify all threads waiting on <Thread_Condition>.
void notify_all () { cond_broadcast (&cond_);
}
private:
// Solaris condition variable.
cond_t cond_;
// Reference to mutex lock.
const Thread_Mutex &m_obMutex;
};

Thread_Condition 的實現(xiàn)與操作系統(tǒng)提供的 API 密切相關(guān),以上的例子是基于 Solaris condition variable API 的面向?qū)ο蟮姆庋b。另外,這里的 Thread_Mutex 類型是對操作系統(tǒng)提供的線程鎖的面向?qū)ο蟮姆庋b (Thread_Mutex 類型就是 Guard 模板參數(shù) LOCK 所指向的類型 ) 。

而對于 Java,問題就變得簡單很多,你不需要去封裝自己的條件變量類,Java 的根類 Object 提供了 wait/notify/notifyAll 方法給開發(fā)者,很容易使用,這個我們在后面的討論中會看到。




Monitor Object 設(shè)計模式 C++ 描述

我們將從以下幾個方面來討論 Monitor Object 模式。

問題描述

我們在開發(fā)并發(fā)的應(yīng)用時,經(jīng)常需要設(shè)計這樣的對象,該對象的方法會在多線程的環(huán)境下被調(diào)用,而這些方法的執(zhí)行都會改變該對象本身的狀態(tài)。為了防止競爭條件 (race condition) 的出現(xiàn),對于這類對象的設(shè)計,需要考慮解決以下問題:

  • 在任一時間內(nèi),只有唯一的公共的成員方法,被唯一的線程所執(zhí)行。
  • 對于對象的調(diào)用者來說,如果總是需要在調(diào)用方法之前進行拿鎖,而在調(diào)用方法之后進行放鎖,這將會使并發(fā)應(yīng)用編程變得更加困難。合理的設(shè)計是,該對象本身確保任何針對它的方法請求的同步被透明的進行,而不需要調(diào)用者的介入。
  • 如果一個對象的方法執(zhí)行過程中,由于某些條件不能滿足而阻塞,應(yīng)該允許其它的客戶端線程的方法調(diào)用可以訪問該對象。

我 們使用 Monitor Object 設(shè)計模式來解決這類問題:將被客戶線程并發(fā)訪問的對象定義為一個 monitor 對象??蛻艟€程僅僅通過 monitor 對象的同步方法才能使用 monitor 對象定義的服務(wù)。為了防止陷入競爭條件,在任一時刻只能有一個同步方法被執(zhí)行。每一個 monitor 對象包含一個 monitor 鎖,被同步方法用于串行訪問對象的行為和狀態(tài)。此外,同步方法可以根據(jù)一個或多個與 monitor 對象相關(guān)的 monitor conditions 來決定在何種情況下掛起或恢復(fù)他們的執(zhí)行。

結(jié)構(gòu)

在 Monitor Object 模式中,主要有四種類型的參與者:

  • 監(jiān)視者對象 (Monitor Object): 負責定義公共的接口方法,這些公共的接口方法會在多線程的環(huán)境下被調(diào)用執(zhí)行。
  • 同步方法:這些方法是監(jiān)視者對象所定義。為了防止競爭條件,無論是否同時有多個線程并發(fā)調(diào)用同步方法,還是監(jiān)視者對象含有多個同步方法,在任一時間內(nèi)只有監(jiān)視者對象的一個同步方法能夠被執(zhí)行。
  • 監(jiān)視鎖 (Monitor Lock): 每一個監(jiān)視者對象都會擁有一把監(jiān)視鎖。
  • 監(jiān)視條件 (Monitor Condition): 同步方法使用監(jiān)視鎖和監(jiān)視條件來決定方法是否需要阻塞或重新執(zhí)行。

執(zhí)行序列圖

在監(jiān)視者對象模式中,在參與者之間將發(fā)生如下的協(xié)作過程:

1、 同步方法的調(diào)用和串行化。當客戶線程調(diào)用監(jiān)視者對象的同步方法時,必須首先獲取它的監(jiān)視鎖。只要該監(jiān)視者對象有其他同步方法正在被執(zhí)行,獲取操作便不會成 功。在這種情況下,客戶線程將被阻塞直到它獲取監(jiān)視鎖。當客戶線程成功獲取監(jiān)視鎖后,進入臨界區(qū),執(zhí)行方法實現(xiàn)的服務(wù)。一旦同步方法完成執(zhí)行,監(jiān)視鎖會被 自動釋放,目的是使其他客戶線程有機會調(diào)用執(zhí)行該監(jiān)視者對象的同步方法。

2、同步方法線程掛起。如果調(diào)用同步方法的客戶線程必須被阻塞或是有其他原因不能立刻進行,它能夠在一個監(jiān)視條件上等待,這將導致該客戶線程暫時釋放監(jiān)視鎖,并被掛起在監(jiān)視條件上。

3、監(jiān)視條件通知。一個客戶線程能夠通知一個監(jiān)視條件,目的是為了讓一個前期使自己掛起在一個監(jiān)視條件上的同步方法線程恢復(fù)運行。

4、同步方法線程恢復(fù)。一旦一個早先被掛起在監(jiān)視條件上的同步方法線程獲取通知,它將繼續(xù)在最初的等待監(jiān)視條件的點上執(zhí)行。在被通知線程被允許恢復(fù)執(zhí)行同步方法之前,監(jiān)視鎖將自動被獲取。圖 1 描述了 監(jiān)視者 對象的動態(tài)特性。


圖 1. Monitor Object Sequence Diagram.
圖 1. Monitor Object Sequence Diagram

示例

在本節(jié)中,我們將使用監(jiān)視者對象設(shè)計模式來解決一個實際的問題。

這是一個典型的生產(chǎn)者 / 消費者模式問題。假定我們有一個固定長度的消息隊列,該隊列會被多個生產(chǎn)者 / 消費者線程所操作,生產(chǎn)者線程負責將消息放入該隊列,而消費者線程負責從該對列中取出消息。


清單 6. Message_Queue.h
            
class Message_Queue {
public:
enum { MAX_MESSAGES = 100/* ... */ };
// The constructor defines the maximum number
// of messages in the queue. This determines when the queue is 'full.'
Message_Queue(size_t max_messages = MAX_MESSAGES);
virtual ~Message_Queue();
// Put the <Message> at the tail of the queue.
// If the queue is full, block until the queue is not full.
/* synchronized */
void put (const Message &msg);
// Get the <Message> from the head of the queue
// and remove it. If the queue is empty, block until the queue is not empty.
/* synchronized */
Message get();
// True if the queue is empty, else false.
/* synchronized */
bool empty () const;
// True if the queue is full, else false.
/* synchronized */
bool full () const;
private:
// Put the <Message> at the tail of the queue, and
// get the <Message> at its head, respectively.
// Note that, the internal methods are not synchronized.
void put_i (const Message &msg);
Message get_i ();
// True if the queue is empty, else false.
bool empty_i () const;
// True if the queue is full, else false.
bool full_i () const;
private:
// Internal Queue representation omitted, could be a
// circular array or a linked list, etc.. ...
// Current number of <Message>s in the queue.
size_t message_count_;
// The maximum number <Message>s that can be
// in a queue before it's considered 'full.'
size_t max_messages_;
// Monitor lock that protects the queue's
// internal state from race conditions during concurrent access.
mutable Thread_Mutex monitor_lock_;
// Condition variable used in conjunction with <monitor_lock_> to make
// synchronized method threads wait until the queue is no longer empty.
Thread_Condition not_empty_;
// Condition variable used in conjunction with <monitor_lock_> to make
// synchronized method threads wait until the queue is no longer full.
Thread_Condition not_full_;
};


清單 7. Message_Queue.cpp
            
#include "Message_Queue.h"
Message_Queue::Message_Queue (size_t max_messages)
:not_full_(monitor_lock_),
not_empty_(monitor_lock_),
max_messages_(max_messages),
message_count_(0) {
}
bool Message_Queue::empty () const {
Guard<Thread_Mutex> guard (monitor_lock_);
return empty_i ();
}
bool Message_Queue::full () const {
Guard<Thread_Mutex> guard (monitor_lock_);
return full_i ();
}
void Message_Queue::put (const Message &msg) {
// Use the Scoped Locking idiom to acquire/release the < monitor_lock_> upon
// entry/exit to the synchronized method.
Guard<Thread_Mutex> guard (monitor_lock_);
// Wait while the queue is full.
while (full_i ()) {
// Release < monitor_lock_> and suspend the
// calling thread waiting for space in the queue.
// The <monitor_lock_> is reacquired automatically when <wait> returns.
not_full_.wait ();
}
// Enqueue the <Message> at the tail.
put_i (msg);
// Notify any thread waiting in <get> that the queue has at least one <Message>.
not_empty_.notify ();
} // Destructor of <guard> releases <monitor_lock_>.
Message Message_Queue::get () {
// Use the Scoped Locking idiom to acquire/release the <monitor_lock_> upon
// entry/exit to the synchronized method.
Guard<Thread_Mutex> guard (monitor_lock_);
// Wait while the queue is empty.
while (empty_i ()) {
// Release <monitor_lock_> and suspend the
// calling thread waiting for a new <Message> to
// be put into the queue. The <monitor_lock_> is
// reacquired automatically when <wait> returns.
not_empty_.wait ();
}
// Dequeue the first <Message> in the queue and update the <message_count_>.
Message m = get_i ();
// Notify any thread waiting in <put> that the
// queue has room for at least one <Message>.
not_full_.notify ();
return m;
} // Destructor of <guard> releases <monitor_lock_>.
bool Message_Queue::empty_i () const {
return message_count_ == 0;
}
bool Message_Queue::full_i () const {
return message_count_ == max_messages_;
}
Message_Queue::~Message_Queue() {
}





Monitor Object Java 實踐

認識 Java Monitor Object

Java Monitor 從兩個方面來支持線程之間的同步,即:互斥執(zhí)行與協(xié)作。 Java 使用對象鎖 ( 使用 synchronized 獲得對象鎖 ) 保證工作在共享的數(shù)據(jù)集上的線程互斥執(zhí)行 , 使用 notify/notifyAll/wait 方法來協(xié)同不同線程之間的工作。這些方法在 Object 類上被定義,會被所有的 Java 對象自動繼承。

實質(zhì)上,Java 的 Object 類本身就是監(jiān)視者對象,Java 語言對于這樣一個典型并發(fā)設(shè)計模式做了內(nèi)建的支持。不過,在 Java 里,我們已經(jīng)看不到了我們在 C++ 一節(jié)所討論的區(qū)域鎖與條件變量的概念。下圖很好地描述了 Java Monitor 的工作機理。


圖 2. Java Monitor
圖 2. Java Monitor

線程如果獲得監(jiān)視鎖成功,將成為該監(jiān)視者對象的擁有者。在任一時刻內(nèi),監(jiān)視者對象只屬于一個活動線程 (Owner) 。擁有者線程可以調(diào)用 wait 方法自動釋放監(jiān)視鎖,進入等待狀態(tài)。

示例

在本節(jié),我們將用 Java Monitor 來重新解決用 C++ 實現(xiàn)的生產(chǎn)者 / 消費者模式問題。


清單 8. Message Class
            
public class Message {
private static int OBJ_COUNT = 0;
public int obj_index_;
Message(){
synchronized(Message.class) {
OBJ_COUNT++;
obj_index_ = OBJ_COUNT;
}
}

@Override
public String toString() {
return "message["+obj_index_+"]";
}
}


清單 9. MessageQueue Class
            
public class MessageQueue {
private int message_count_;
private int max_messages_;
private Message[] buffer_;

private int in_ = 0, out_ = 0;
public MessageQueue(int max_messages) {
max_messages_ = max_messages;
message_count_ = 0;
buffer_ = new Message[max_messages_];
}

synchronized boolean full () {
return full_i ();
}
synchronized void put (Message msg) {
while (full_i ()) {
try {
System.out.println("thread["+
Thread.currentThread().getId()+
"]"+
"release monitor lock, wait for space in the queue");
wait();
} catch (InterruptedException e) {
//do something.
} finally {
//do something.
}
}//end while.
put_i(msg);
notifyAll();
}
synchronized Message get() {
while (empty_i ()) {
try {
System.out.println("thread["+
Thread.currentThread().getId()+
"]"+
"release monitor lock, wait for message in the queue");
wait();
} catch (InterruptedException e) {
//do something.
} finally {
//do something.
}
}//end while.
Message m = get_i ();
notifyAll();
return m;
}
private boolean empty_i () {
return message_count_ == 0;
}
private boolean full_i () {
return message_count_ == max_messages_;
}
private void put_i (Message msg) {
System.out.println("thread ["+
Thread.currentThread().getId()+
"] put message <"+
msg+
">" +
"to the queue");
buffer_[in_] = msg;
in_ = (in_ + 1) % max_messages_;
message_count_++;
}
private Message get_i() {
Message msg = buffer_[out_];
out_= (out_ + 1) % max_messages_;
message_count_--;
System.out.println("thread ["+
Thread.currentThread().getId()+
"] get message <"+
msg+
">" +
"from the queue");
return msg;
}
}

在 Java 的示例中,沒有放更多的注釋,希望讀者通過對照 C++ 的示例,來閱讀理解這里的 Java 代碼??梢钥吹?,使用 Java 的版本代碼簡潔了很多。另外,這里提供的 Java 代碼,稍作修改,是直接可以作為獨立的 Java 程序運行的。




總結(jié)

我們對比一下 Monitor Object 設(shè)計模式的 C++ 版本與 Java 版本,做出如下的總結(jié)。

在 Java 的版本中,我們不需要親自開發(fā) Scoped Lock,Thread Condition 類,Java 語言給我們提供了內(nèi)建的支持,我們很容易使用 synchronized, wait/notify 這些 Java 特性來構(gòu)建基于 Monitor Object 模式的應(yīng)用。而缺點是:缺乏一些必要的靈活性。比如 : 在 Java 的版本中,我們并不能區(qū)分出 not empty 與 not full 這兩個條件變量,所以我們只能使用 notifyAll 來通知所有等待者線程,而 C++ 版本使用了不同的通知喚醒:not_full_.notify 與 not_empty_.notify 。同樣,在 Java 中對于 synchrnonized 的使用,后面一定要跟 {} 語句塊,這在代碼的書寫上有些不靈活,而在 C++ 中的,Scoped Lock 默認就是保護當前的語句塊,當然你也可以選擇使用 {} 來顯式聲明。而且,使用 synchroninzed 所獲得的對象鎖,無法細粒度地區(qū)分是獲得讀鎖還是寫鎖。

不過總的來說,Java 的確簡化了基于 Monitor Object 并發(fā)模式的開發(fā)。不過,我們應(yīng)該意識到,并發(fā)的實際應(yīng)用開發(fā)決不會像 Java 語法這么體現(xiàn)出來的簡單,簡潔。我們更應(yīng)該看到并發(fā)應(yīng)用程序本質(zhì)的一些東西,這有利于幫助我們構(gòu)建更加健壯的并發(fā)應(yīng)用。



參考資料



關(guān)于作者


李三紅任職于 IBM CDL,負責 Lotus Notes 產(chǎn)品研發(fā)。

探索 Java 同步機制


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 免费观看成人碰视频公开 | 亚洲欧美另类在线 | 337p欧洲日本大胆艺术 | 久草网视频在线观看 | 欧美在线播放成人免费 | 91福利国产在线观看 | 国产精品第8页 | 亚洲综合成人网在线观看 | 一级毛片看看 | 久热re在线视频精品免费 | 欧美国产成人一区二区三区 | 妖精视频永久在线入口 | 未成人做爰视频www 我爱52avαv永久网站 | 色女影院| 99热久久这里只有精品首页 | 久久99热久久精品23 | 欧美视频亚洲视频 | 久久机热一这里只精品 | 不卡的在线视频免费观看 | 亚洲综合日韩 | 深夜在线影院 | 久久久日韩精品国产成人 | 免费人成激情视频在线观看冫 | 思99热精品久久只有精品 | 日本高清在线精品一区二区三区 | 快射视频网| 亚洲综合图片人成综合网 | 天天操天天射天天色 | 91色综合综合热五月激情 | 国产精品短篇二区 | 亚洲欧美中日韩中文字幕 | 国产精品视频免费在线观看 | 免费看搡女人的视频 | 亚洲婷婷综合色高清在线 | 日日噜噜噜夜夜爽爽狠狠图片 | 国产高清在线观看麻豆 | 免费看一区二区三区 | 国产精品9999久久久久 | 香蕉伊人网 | 精品久久久久久免费影院 | 999精品免费视频观看 |