Implicit & Explicit Locks
Introduction
In Computer Science, synchronization refers to relationships among events. For example:
- Serialization: Event A must happen before Event B.
- Mutual Exclusion: Event A and Event B must not happen at the same time.
Java’s support for synchronization is based on implicit locks by the use of synchronized keyword. Every object has an intrinsic lock associated with it. When a thread invokes a synchronized non-static method, it acquires the intrinsic lock for that method’s object. The lock is returned when the method returns (even due to an uncaught exception). Similarly, every class has an intrinsic lock associated with it. A thread acquires this class lock to invoke a synchronized static method and releases when the method returns. These locks are acquired and released automatically.
In addition to intrinsic locks, each instance also has a wait set associated with it. When a thread invokes wait() (or any overloaded wait(...) methods) it is placed in this wait set. The wait(...) methods must be called from a synchronized block. This is used in scenarios like when a thread obtains the lock but is not ready to proceed code in the critical section.
Implicit locks demo
I will demonstrate implicit locks by implementing a solution to producer-consumer problem. Source code is attached as a link at the end of the article. The producer-consumer problem can be described as follows. There are M threads producing items and N threads consuming items. They share a common buffer for transfering these items. The problem is to make sure that producers won’t add items when the buffer is full and consumers won’t remove items from an empty buffer. In ImplicitLocksDemo class I have 3 nested classes – Producer, Consumer and Drop (shared buffer). Producer and Consumer are implemented as threads. Producer creates a String object and places in the drop object which is shared between producers and consumers as shown below:
22 23 24 25 26 27 28 29 30 31 32 33 | public static void main(String s[]) { Drop drop = new Drop(); Consumer c1 = new Consumer("C1", drop); Producer p1 = new Producer("P1", drop); Consumer c2 = new Consumer("C2", drop); Producer p2 = new Producer("P2", drop); new Thread(p1).start(); new Thread(c1).start(); new Thread(p2).start(); new Thread(c2).start(); } |
To make the problem a bit more complicated, the shared buffer is made to hold only one item. The Drop class has one member variable (shared) which will be set by a producer. Additionally, the Drop class has a variable called empty which tells if the buffer is full or empty. Producers can only set shared with an item when empty is true. Similarly, consumers can only read shared when empty is false. The Drop class has two methods called get() which the consumers call to read an item and a method called set(String) which is called by producers to set an item. Both get() and set() are made synchronized so only one thread can invoke get() or set() method at any time.
Now, what if a producer acquired a lock and wants to invoke set() method and the buffer is not empty? If a consumer had not read the item produced previously, the producer would be overwriting the item. To use the locks effectively, we use wait/notify mechanism provided by Java. wait() and notify are methods defined in Object class, hence, they are available to any class. Invoking wait() makes the current thread go into a waiting state or into the wait set associated with the object on which wait() was invoked. When a consumer reads the item it invokes notify() on drop object which sends a signal to all threads waiting in the wait set of drop object. That is to say that the shared object is now empty and a producer can safely fill the shared storage. So with the help of wait/notify, threads can effectively communicate with each other and share the critical sections in a safe manner. Complete solution is given below.
/** * @(#)ImplicitLocksDemo.java Feb 26, 2008 */ package lock; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Demonstrates Producer and Consumer with a * * @author $Author: vijaykandy $ * @version $Revision: 1.4 $ */ public class ImplicitLocksDemo { /** * Main. * * @param s */ public static void main(String s[]) { Drop drop = new Drop(); Consumer c1 = new Consumer("C1", drop); Producer p1 = new Producer("P1", drop); Consumer c2 = new Consumer("C2", drop); Producer p2 = new Producer("P2", drop); new Thread(p1).start(); new Thread(c1).start(); new Thread(p2).start(); new Thread(c2).start(); } /** * Shared class * * @author $Author: vijaykandy $ * @version $Revision: 1.4 $ */ static class Drop { private String shared; private boolean empty = true; /** * Takes a message when a signal is received. * * @return */ public synchronized String get() { while (empty) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } String message = shared; empty = true; this.notifyAll(); return message; } /** * Sets a message when a signal is received. * * @param s */ public synchronized void set(String s) { while (!empty) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } shared = s; empty = false; this.notifyAll(); } } /** * Produces a message * * @author $Author: vijaykandy $ * @version $Revision: 1.4 $ */ static class Producer implements Runnable { private String name; private Drop drop; private Random random = new Random();; private List<String> messages = new ArrayList<String>() { { add(Integer.toHexString(random.nextInt(1000))); add(Integer.toHexString(random.nextInt(1000))); add(Integer.toHexString(random.nextInt(1000))); add("DONE"); } }; public Producer(String name, Drop drop) { this.name = name; this.drop = drop; } public void run() { Random random = new Random(); for (String msg : messages) { drop.set(msg); System.out.format("%s produced: %s%n", name, msg); try { Thread.sleep(random.nextInt(1000)); } catch (InterruptedException e) { } } } } /** * Consumes a message * * @author $Author: vijaykandy $ * @version $Revision: 1.4 $ */ static class Consumer implements Runnable { private String name; private Drop drop; public Consumer(String name, Drop drop) { this.name = name; this.drop = drop; } public void run() { Random random = new Random(); while (true) { String message = drop.get(); System.out.format("%s received: %s%n", name, message); if (message.equals("DONE")) { return; } try { Thread.sleep(random.nextInt(1000)); } catch (InterruptedException e) { } } } }
The synchronized keyword can also be used with a block. In this case, threads are required to acquire the lock associated with the object in the synchronized statement as shown here:.
public void m() { Object anObject = ... // ... synchronized(anObject) { // threads must acquire anObject's lock } // ... }









Leave your response!