Explore Java synchronization mechanism

Source: Internet
Author: User

This article starts with the typical design pattern of monitor object and explores the synchronization mechanism of Java from a new perspective.

This article will elaborate on two aspects:

  1. Enable
    Use C ++ to describe the design mode of the monitor object. Java provides a good language encapsulation for such a typical mode.
    For developers, many things about the mode itself are blocked. This article attempts to use the native C ++ language to help readers to monitor object in essence.
    The design pattern has a more comprehensive understanding.
  2. Combined with the c ++ version of the monitor object design pattern, readers can have a deeper understanding of the Java synchronization mechanism, helping readers correctly and effectively use the Java synchronization mechanism.

Prerequisites

Before you start the formal discussion, you need to have some preliminary knowledge.

What is raiI?

Capital
Source retrieval is initialization (raiI, resource acquisition is initialization), which means to obtain resources in the constructor of an object.
,
And release it in the destructor of the object. This resource can be an object, memory, file handle, or other type. To implement this function, we will say that it uses the resource acquisition, that is, the initialization (raiI) class.
Method. RaiI is a typical language idiom used by many oo languages. The following is an example of C ++.

Listing 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 ...
};

Enable
The advantage of raiI is that because the Destructor is automatically called by the system, it can help us to automatically and implicitly release the resources we get. Many familiar C ++
Technology uses this design pattern, such as smart pointer and scoped lock ).

Unlike C ++, Java objects do not have destructor. Java System provides GC to manage memory resources. For resources such as database connections and sockets, Java provides finalize () for processing. However, note that the finalizer and C ++ destructor of Java are different. The finalize () function is called asynchronously by GC at an appropriate time, we cannot use finalize () to implement raiI in C ++. The general practice is to use the finally statement block provided by Java.

Listing 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(); }
}

What is scoped lock)

A zone lock means that when a thread enters a zone, a lock is automatically acquired. When the thread executes and leaves this zone, the lock is automatically released. The implementation of the C ++ regional lock uses the raiI technology. The implementation is as follows.

Listing 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 is a template class. The lock type refers to the abstraction of the thread lock provided by the operating system. For example, on a Windows platform, lock can be an encapsulation of critical_section.

So how to implement the regional lock for Java? Don't worry. Java has encapsulated the region lock mode at the language level. Therefore, Java developers do not have to develop their own regional lock classes like C ++, this is the familiar synchronized keyword.

Listing 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 ensures that the object lock is obtained after entering the region. No matter where the function exits, the object lock will be correctly released.

What is conditional variables)

Entries
A component variable is usually used by a thread to wait until a conditional expression involving shared data reaches a specific State. When another collaboration thread indicates that the status of the shared data has changed, the scheduler will wake up in
The thread suspended on the condition variable. Therefore, the newly awakened thread recalculates its conditional expression. If the shared data has reached the appropriate state, it will resume processing. The following is the c ++ Implementation of conditional variables.

Listing 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
The implementation is closely related to the APIS provided by the operating system. The preceding example is based on the Solaris condition variable API.
Object-oriented encapsulation. In addition, the thread_mutex type here is the object-oriented encapsulation of the thread lock provided by the operating system (the thread_mutex type is
The type indicated by the guard template parameter lock ).

For Java, the problem becomes much simpler. You do not need to encapsulate your own Conditional Variable classes. The Java root class Object provides the wait/notify/policyall Method for developers and is easy to use, we will see this in later discussions.


Monitor Object Design Mode C ++ description

We will discuss the monitor object mode from the following aspects.

Problem description

When developing concurrent applications, we often need to design such an object. The method of this object will be called in a multi-threaded environment, the execution of these methods changes the state of the object. In order to prevent the appearance of race condition, the following problems need to be solved for the design of such objects:

  • At any time, only the only common member method is executed by the unique thread.
  • For object callers, if they always need to get the lock before calling the method, and put the lock after calling the method, this will make concurrent application programming more difficult. A reasonable design is that the object itself ensures that any synchronous request for its method is transparent without the caller's intervention.
  • If an object is blocked because some conditions cannot be met during method execution, you should allow method calls from other client threads to access the object.

Me
We use the monitor object design mode to solve these problems: Define the objects that are concurrently accessed by the client thread as a monitor object. The customer thread only uses
Only the synchronization method of the monitor object can use the service defined by the monitor object. To prevent competition, only one synchronization method can be executed at any time. Every
The monitor object contains a monitor lock, which is used by the synchronization method to access the behavior and status of objects in a series. In addition, the synchronization method can be based on one or more
Monitor conditions related to objects to decide under which circumstances to suspend or resume their execution.

Structure

In the monitor object mode, there are four main types of participants:

  • Monitor object: defines public interface methods, which are called and executed in a multi-threaded environment.
  • Synchronization Methods: these methods are defined by the monitoring object. To prevent competition, whether there are multiple threads simultaneously calling the synchronous method or the Monitored object contains multiple synchronous methods, only one synchronization method of the Monitored object can be executed at any time.
  • Monitor lock: Each monitor object has a monitor lock.
  • Monitoring condition: the synchronization method uses the monitoring lock and monitoring condition to determine whether the method needs to be blocked or re-executed.

Execution sequence diagram

In the Monitored object mode, the following collaboration process occurs between participants:

1,
Call and serialization of synchronous methods. When the client thread calls the synchronous method of the Monitored object, it must first obtain its monitoring lock. As long as the Monitored object has other Synchronization Methods being executed
. In this case, the client thread will be blocked until it gets the monitoring lock. After the customer thread successfully acquires the monitoring lock, it enters the critical section and executes the service implemented by the method. Once the synchronization method is completed, the monitoring lock will be
Automatic release is used to enable other customer threads to call the synchronous method for executing the Monitored object.

2. The synchronization method thread is suspended. If the client thread that calls the synchronous method must be blocked or cannot be performed immediately for other reasons, it can wait on a monitoring condition, which causes the client thread to temporarily release the monitoring lock, and is suspended on the monitoring condition.

3. Monitoring condition notification. A client thread can notify a monitoring condition to resume a synchronization method thread that suspends itself on a monitoring condition in the early stage.

4. Synchronization Method thread recovery. Once a synchronous method thread suspended on the monitoring condition gets the notification, it will continue to execute at the initial waiting for the monitoring condition. The monitoring lock is automatically acquired before the notification thread is allowed to resume the synchronization method. Figure 1 describesMonitoredDynamic Characteristics of objects.

Figure 1. Monitor Object Sequence dience.

Example

In this section, we will use the monitoring object design pattern to solve a practical problem.

This is a typical producer/consumer model problem. Assume that we have a fixed-length message queue, which is operated by multiple producer/consumer threads, and the producer thread is responsible for putting messages into the queue, the consumer thread is responsible for extracting messages from this pair column.

Listing 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_;
};

Listing 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 practices

Understanding Java monitor object

Java
Monitor supports synchronization between threads in two aspects: mutex execution and collaboration. Java uses the object lock (use synchronized to obtain the object lock)
Ensure that the threads working on the shared dataset are mutually exclusive and use the Y/notifyall/Wait method to coordinate the work between different threads. These methods are
The object class is defined and will be automatically inherited by all Java objects.

Essentially, Java object
The class itself is the Monitored object. The Java language provides built-in support for such a typical concurrent design mode. However, in Java, we can no longer see
This section describes the concepts of regional locks and conditional variables. The working mechanism of Java monitor is well described.

Figure 2. Java Monitor

If the thread successfully acquires the monitoring lock, it becomes the owner of the Monitored object. The Monitored object belongs to only one active thread (owner) at any time ). The owner thread can call the wait method to automatically release the monitoring lock and enter the waiting state.

Example

In this section, we will use Java monitor to solve the problem of producer/consumer Mode Implemented in C ++.

Listing 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_+"]";
}
}

Listing 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;
}
}

In the Java example, no more comments are provided. We hope that you can read and understand the Java code by referring to the C ++ example. The Java version code is much simpler. In addition, the Java code provided here is slightly modified and can be directly run as an independent Java program.


Summary

Let's compare the C ++ and Java versions of the monitor object design pattern, and make the following summary.

In
In the Java version, we do not need to develop scoped lock, thread condition class, Java
The language provides us with built-in support. We can easily use the synchronized, wait/notify Java features to build a monitor-based
Object Mode. The disadvantage is: lack of necessary flexibility. For example, in Java, we cannot distinguish not empty from not
Full, so we can only use policyall to notify all the waiting threads, while C ++
Different Versions of notifications are used to wake up: not_full _. Notify and not_empty _. Notify. Similarly, in Java
The use of synchrnonized must be followed by the {} statement block, which is somewhat inflexible in code writing. In C ++, scoped lock
By default, the current statement block is protected. You can also use {} to explicitly declare the block. In addition, synchroninzed is used.
The obtained Object locks cannot be divided into read locks or write locks in fine-grained regions.

But in general, Java does simplify the process
Development of object concurrency mode. However, we should realize that concurrent application development will never be like Java
The syntax is simple and concise. We should be more aware of the essence of concurrent applications, which helps us build more robust concurrent applications.

References

  • "Java singleton object synchronization problem discussion" (developerworks, November December 2003): This article will discuss in the multi-threaded environment, several synchronization problems may occur when you use a single instance object for configuration information management, and an optional solution is provided for each problem.

  • "Java Theory and Practice: synchronization optimization in Mustang" (developerworks, November 2005): This article describes some synchronization optimization arrangements for Mustang.
  • Developerworks Java Technology Zone: there are hundreds of articles on Java programming.

About the author

Li sanhong works for ibm cdl and is responsible for Lotus Notes product development.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.