Monday, October 21, 2013

Locks in Java : Concurrency

Explicit locking mechanism can be used to coordinate access to shared resources in a multi-threaded environment without using the keyword synchronized. The Lock interface, which is declared in the java.util.concurrent.locks package, defines the explicit locking operations. The ReentrantLock class, in the same package, is the concrete implementation of the Lock interface.
In this post, we look into java.util.concurrent.locks package and see how it is useful and why such package is introduced. This is one of the low level API other than java.util.concurrent.atomic.

Locks
Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.

The Lock interface is declared as follows:

The use of the lock() method to acquire a lock behaves the same as the use of the synchronized keyword. The use of the synchronized keyword requires that a thread should acquire and release an object’s monitor lock in the same block of code. When you use the synchronized keyword to acquire an object’s monitor lock, the lock is released by the JVM when the program leaves the block in which the lock was acquired. This feature makes working with intrinsic locks very simple and less error prone. However, in the case of the Lock interface, the restriction of acquiring and releasing of the lock in the same block of code does not apply. This makes it a little flexible to use; however, it is more error prone because the responsibility of acquiring as well as releasing the lock is on the programmer.

You must make sure that you release the lock by calling the unlock() method of the Lock interface after you are done with the lock.The use of a try-finally block is necessary in this case because no matter how you finish returning from this method after you call myLock.lock(), you would like to release the lock. This can be assured only if you place the call to the unlock() method inside the finally block.

You may wonder why you would use the code structure when you could have used the synchronized keyword to achieve the same effect, like so:
You are correct in thinking that using the synchronized keyword would have been better in this case. It is much simpler and less error prone to use the synchronized keyword in such situations. The power of using the new Lock interface becomes evident when you come across situations where using the synchronized keyword is not possible or very cumbersome.
For example, if you want to acquire the lock in the updateResource() method and release it in some other methods, you cannot use the synchronized keyword. If you need to acquire two locks to work with a shared resource and if only one lock is available, you want to do something else rather than waiting for the other lock. If you use the synchronized keyword or the lock() method of the Lock interface to acquire a lock, the call blocks if the lock is not available immediately, which gives you no option to back off once you asked for the lock. Such blocked threads cannot be interrupted either.

The two methods of the Lock interface, tryLock() and lockInterruptibly(), give you the ability to try to acquire a lock (rather than acquire a lock or block). The thread that has acquired the lock can be interrupted if it is blocked. The syntax to acquire and release a lock using the Lock interface should use a try-finally or a try-catch-finally block structure to avoid unintended bugs by placing the unlock() call in a finally block.

Everybody know the main advantage of using the synchronized keyword (implicit locking) is that you don’t have to remember to release the lock in a finally block since, at the end of the synchronized block (or method), code will be generated to automatically release the lock.
Although this is a useful feature, there are some situations where you may need to control the release of the lock manually.
Lock objects provide this flexibility. However, it is your responsibility to ensure that you release the lock in a finally block while using Lock objects

Snippet describes the usage idiom for a Lock:

Lock lock = /* get Lock type instance */;
 lock.lock();

 try {
  // critical section
 }
 finally {
  lock.unlock();
 }

Another difference between implicit locks and explicit Lock objects is that you can do a “non-blocking attempt” to acquire locks with Locks.
Non-blocking attempt means You get a lock if that lock is available for locking, or you can back out from requesting the lock using the tryLock() method on a Lock object. If you acquire the lock successfully, then you can carry out the task to be carried out in a critical section; otherwise you execute an alternative action.

It is noteworthy that an overloaded version of the tryLock() method takes the timeout value as an argument so that you can wait to acquire the lock for the specified time.
tryLock(long time, TimeUnit unit)

Snippet describes the usage idiom for a Lock with tryLock():

Lock lock = /* get Lock type instance */;
 if(tryLock()) {
  try {
   // critical section
  }
  finally {
   lock.unlock();
  }
 }
 else {

 }

Using tryLock() helps avoid some of the thread synchronization-related problems such as deadlocks and livelocks. Click to see what is deadlock 

Example
We will solve a classic synchronization problem known as the dining-philosophers problem using the explicit lock constructs. The problem goes like this: five philosophers spend all of their time either thinking or eating. They sit around a circular table with five chairs and five forks. There are only five forks and all five philosophers need to pick the two nearest (one from his left and one from his right) forks to eat.
Once a philosopher finishes eating, he puts down both forks and starts thinking. A philosopher cannot pick up a fork if his neighbor is using it. What happens if each of the five philosophers picks up one fork from his right and waits for his left fork to be released by his neighbor? This would be a deadlock situation and no philosopher would be able to eat. This deadlock condition can be avoided easily by using the tryLock() method of the Lock interface. This method returns immediately and it never blocks. If the lock is available, it gets the lock and returns true. If the lock is not available, it returns false.

To create a philosopher, you would use code like:
Lock fork1 = new ReentrantLock();
..
Lock fork5 = new ReentrantLock();

Philosopher p1 = new Philosopher(fork1, fork2, "A");
...
Philosopher p5 = new Philosopher(fork5, fork1, "E");

You run all five philosophers in five different threads to simulate the dining-philosophers problem. Read the code in the eat() method carefully. It tries to get the left and right forks one at a time. If you can get only one fork and not the other, you put down the one you got so others can have it. The code in the eat() method has only the logic to get the forks. In a real program, if you cannot get both forks, you would like to wait for some time and try again to pick up the forks. You will have to write that logic.


A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities



Related Post
How thread exchange data in java

Best way to synchronize primitive datatypes in java
Basic introduction to thread in detail
Collection Framework overview with 2 pictures



If you know anyone who has started learning java, why not help them out! Just share this post with them.  Thanks for studying today!...

No comments:

Post a Comment