Lock is your tool to guard shared resource which can be anything e.g. database, File system, a Prime number Generator or a Message processor. Before using Locks in Java program, it’s also better to learn some basics. Lock is an interface from java.util.concurrent package. It was introduced in JDK 1.5 release as an alternative of synchronized keyword. If you have never written any multi-threading program, then I suggest first start with synchronized keyword because it’s easier to use them. Once you are familiar with working of multi-threading program e.g. How threads share data, how inter thread communication works, you can start with Lock facility. As I told you Lock is an interface, so we cannot use it directly, instead we need to use its implementation class. Thankfully Java comes with two implementation of java.util.concurrent.locks.Lock interface, ReentrantLock and ReentrantReadWriteLock, later provides two more inner implementation known as ReentrantReadWriteLock.ReadLock and ReentrantReadWriteLock.WriteLock. For our simple multi-threaded Java program's purpose ReentrantLock is enough.
Here is the idiom to use Locks in Java :
Lock is used to protect a resource, so that only one thread can access it at a time. Why we do that? to make sure our application behave properly. For example we can use Lock to protect a counter, whose sole purpose is to return a count incremented by one, when anyone calls its getCount() method. If we don't protect them by parallel access of thread, then it’s possible that two thread receives same count, which is against the program's policies. Now, coming back to semantics, we have used lock() method to acquire lock and unlock() method to release lock. Always remember to release lock in finally block, because every object has only one lock and if a thread doesn't release it then no one can get it, which may result in your program hung or threads going into deadlock. That's why I said that synchronized keyword is simpler than lock, because Java itself make sure that lock acquired by thread by entering into synchronized block or method is released as soon as it came out of the block or method. This happens even if thread came out by throwing exception, this is also we have unlock code in finally block, to make sure it run even if try block throws exception or not. In next section we will see example of our multi-threaded Java program, which uses Lock to protect shared Counter.
public class Counter{ private Lock lock = new Lock(); private int count = 0; public int inc(){ lock.lock(); int newCount = ++count; lock.unlock(); return newCount; } } The
Here is a simple
public class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } } Notice the
When the thread is done with the code in the critical section (the code between
public class Reentrant{ public synchronized outer(){ inner(); } public synchronized inner(){ //do something } } Notice how both
The lock implementation shown earlier is not reentrant. If we rewrite the
public class Reentrant2{ Lock lock = new Lock(); public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); } } A thread calling
The reason the thread will be blocked the second time it calls
public class Lock{ boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } ... } It is the condition inside the while loop (spin lock) that determines if a thread is allowed to exit the
To make the
public class Lock{ boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.curentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } ... } Notice how the while loop (spin lock) now also takes the thread that locked the
Additionally, we need to count the number of times the lock has been locked by the same thread. Otherwise, a single call to
The
lock.lock(); try{ //do critical section code, which may throw exception } finally { lock.unlock(); } This little construct makes sure that the
Here is the idiom to use Locks in Java :
Lock is used to protect a resource, so that only one thread can access it at a time. Why we do that? to make sure our application behave properly. For example we can use Lock to protect a counter, whose sole purpose is to return a count incremented by one, when anyone calls its getCount() method. If we don't protect them by parallel access of thread, then it’s possible that two thread receives same count, which is against the program's policies. Now, coming back to semantics, we have used lock() method to acquire lock and unlock() method to release lock. Always remember to release lock in finally block, because every object has only one lock and if a thread doesn't release it then no one can get it, which may result in your program hung or threads going into deadlock. That's why I said that synchronized keyword is simpler than lock, because Java itself make sure that lock acquired by thread by entering into synchronized block or method is released as soon as it came out of the block or method. This happens even if thread came out by throwing exception, this is also we have unlock code in finally block, to make sure it run even if try block throws exception or not. In next section we will see example of our multi-threaded Java program, which uses Lock to protect shared Counter.
A Simple Lock
Let's start out by looking at a synchronized block of Java code:public class Counter{
  private int count = 0;
  public int inc(){
    synchronized(this){
      return ++count;
    }
  }
} 
otice theThesynchronized(this)block in theinc()method. This block makes sure that only one thread can execute thereturn ++countat a time. The code in the synchronized block could have been more advanced, but the simple++countsuffices to get the point across.
Counter class could have been written like this instead, using
    a Lock instead of a synchronized block:
public class Counter{ private Lock lock = new Lock(); private int count = 0; public int inc(){ lock.lock(); int newCount = ++count; lock.unlock(); return newCount; } } The
lock() method locks the Lock instance so that all
    threads calling lock() are blocked until unlock() is
    executed.
Here is a simple
Lock implementation:
public class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } } Notice the
while(isLocked) loop, which is also called a
    "spin lock". Spin locks and the methods wait() and notify()
    are covered in more detail in the text
    Thread Signaling. While isLocked is true,
    the thread calling lock() is parked waiting in the wait() call.
    In case the thread should return unexpectedly from the wait() call without having
    received a notify() call (AKA a Spurious Wakeup)
    the thread re-checks the isLocked condition to see if it is safe to
    proceed or not, rather than just assume that being awakened means it is safe to proceed.
    If isLocked is false, the thread exits the while(isLocked) loop,
    and sets isLocked back to true, to lock the Lock instance for other
    threads calling lock().
When the thread is done with the code in the critical section (the code between
lock() and unlock()), the thread calls unlock().
    Executing unlock() sets isLocked back to false, and notifies (awakens) one of the
    threads waiting in the wait() call in the lock() method, if any.
Lock Reentrance
Synchronized blocks in Java are reentrant. This means, that if a Java thread enters a synchronized block of code, and thereby take the lock on the monitor object the block is synchronized on, the thread can enter other Java code blocks synchronized on the same monitor object. Here is an example:public class Reentrant{ public synchronized outer(){ inner(); } public synchronized inner(){ //do something } } Notice how both
outer() and inner() are declared synchronized,
    which in Java is equivalent to a synchronized(this) block.
    If a thread calls outer() there is no problem calling inner() from inside
    outer(), since both methods (or blocks) are synchronized on the same
    monitor object ("this"). If a thread already holds the lock on a monitor object, it has
    access to all blocks synchronized on the same monitor object. This is called reentrance.
    The thread can reenter any block of code for which it already holds the lock.
The lock implementation shown earlier is not reentrant. If we rewrite the
Reentrant
    class like below, the thread calling outer() will be blocked inside the
    lock.lock() in the inner() method.
public class Reentrant2{ Lock lock = new Lock(); public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); } } A thread calling
outer() will first lock the Lock instance. Then it
    will call inner(). Inside the inner() method the thread will again
    try to lock the Lock instance. This will fail (meaning the thread will be blocked),
    since the Lock instance was locked already in the outer() method.
The reason the thread will be blocked the second time it calls
lock() without
    having called unlock() in between, is apparent when we look at the lock()
    implementation:
public class Lock{ boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } ... } It is the condition inside the while loop (spin lock) that determines if a thread is allowed to exit the
lock() method or not. Currently the condition is that isLocked
    must be false for this to be allowed, regardless of what thread locked it.
To make the
Lock class reentrant we need to make a small change:
public class Lock{ boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.curentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } ... } Notice how the while loop (spin lock) now also takes the thread that locked the
Lock
    instance into consideration. If either the lock is unlocked (isLocked = false) or
    the calling thread is the thread that locked the Lock instance, the while loop
    will not execute, and the thread calling lock() will be allowed to exit the method.
Additionally, we need to count the number of times the lock has been locked by the same thread. Otherwise, a single call to
unlock() will unlock the lock, even if
    the lock has been locked multiple times. We don't want the lock to be unlocked until the
    thread that locked it, has executed the same amount of unlock() calls as
    lock() calls.
The
Lock class is now reentrant.
Lock Fairness
Java's synchronized blocks makes no guarantees about the sequence in which threads trying to enter them are granted access. Therefore, if many threads are constantly competing for access to the same synchronized block, there is a risk that one or more of the threads are never granted access - that access is always granted to other threads. This is called starvation. To avoid this aLock should be fair.
    Since the Lock implementations shown in this text uses synchronized blocks internally, they do not guarantee
    fairness. Starvation and fairness are discussed in more detail in the text
    Starvation and Fairness.
Calling unlock() From a finally-clause
When guarding a critical section with aLock, and the critical section may throw exceptions,
    it is important to call the unlock() method from inside a finally-clause. Doing so makes
    sure that the Lock is unlocked so other threads can lock it. Here is an example:
lock.lock(); try{ //do critical section code, which may throw exception } finally { lock.unlock(); } This little construct makes sure that the
Lock is unlocked in case an
    exception is thrown from the code in the critical section. If unlock()
    was not called from inside a finally-clause, and an exception was thrown
    from the critical section, the Lock would remain locked forever, causing
    all threads calling lock() on that Lock instance to halt
    indefinately.
No comments:
Post a Comment