使用 C++ 语言来描述 Monitor Object 设计模式。Java 对于这样一个典型的模式做了很好的语言层面的封装,因此对于 Java 的开发者来说,很多关于该模式本身的东西被屏蔽掉了。本文试图使用 Native C++ 语言,帮助读者从本质上对 Monitor object 设计模式有一个更全面的认识。
结合 C++ 版本的 Monitor Object 设计模式,引领读者对于 Java 同步机制有一个更深刻的认识,帮助读者正确有效地使用 Java 同步机制。
预备知识
在开始正式讨论之前,需要了解一些预备知识。
什么是 RAII
资源获取即初始化(RAII, Resource Acquisition Is Initialization)是指,在一个对象的构造函数中获得资源 , 并且在该对象的析构函数中释放它。这个资源可以是对象、内存、文件句柄或者其它类型。实现这种功能的类,我们就说它采用了资源获取即初始化(RAII)的方式。 RAII 是一种很典型的语言惯用法,被很多的 OO 语言所使用,下面是 C++ 的例子。
清单 1. RAII Using C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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 ... };
Show moreShow more icon
使用 RAII 的好处是:由于析构函数由系统自动调用,这样可以帮助我们自动地隐式释放我们所获取的资源。事情上,我们熟知的很多 c++ 技术都用到了这一设计模式,比如:智能指针 (Smart Pointer),以及我们接下来要讨论的范围锁 (Scoped Lock) 。
MyResource res = null; try { res = new MyResource(); // Use the resource } finally { //At exit point, close the resource. if (res != null) { res.close(); } }
Show moreShow more icon
什么是区域锁 (Scoped Lock)
区域锁是指线程执行进入一个区域时,一个锁将自动被获取,当该线程执行离开这个区域时,这个锁将被自动释放。 C++ 区域锁的实现使用了 RAII 技术 , 实现如下。
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 ... };
Show moreShow more icon
Guard 是一个模板类,LOCK 类型指的是对操作系统提供的线程锁的抽象,比如,在 Windows 平台上,LOCK 可以是对 CRITICAL_SECTION 的封装。
那么对于 Java,怎么实现区域锁呢?不必担心,Java 对于区域锁模式在语言层面上已经做了封装,所以对于 Java 开发者来说,不必像 C++ 这样来开发自己的区域锁类,这就是我们所熟知的 synchronized 关键字。
清单 4. Scoped Lock Using Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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; }
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_; };
#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() { }
在本节,我们将用 Java Monitor 来重新解决用 C++ 实现的生产者 / 消费者模式问题。
清单 8. Message Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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_+"]"; } }
Reprint policy:
All articles in this blog are used except for special statements
CC BY 4.0
reprint policy. If reproduced, please indicate source
John Doe
!