We
are living in an environment, where systems with multiprocessors is very
common. To leverage these multi processors, application should use multiple
threads. Threads are used to perform parallel tasks. While dealing with shared
resources, you should restrict the access such that at most one thread can able
to access these shared resources and the changes done by the thread must be
visible to other threads. In simple terms, application should maintain
following two properties.
a.
Atomicity
b.
Visibility
What is atomicity?
Only
one thread can able to access the shared resources at a time. You can achieve
this by using synchronization.
What is Visibility?
Updated shared data from one thread are available to other thread when it enters a
synchronized block protected by that same monitor (lock).
Let
me give an example, suppose there are two threads competing for resources, first
thread should get resource1, second thread should get resource2, first thread
should get resource3, second thread should get resource 4……
(T1,
R1), (T2, R2), (T1, R3), (T2, R4), (T1, R5), (T2, R6)………
(T1,
R1) means Thread1 gets resource1
Without
synchronization, it is not possible to allocate resources like above. Following
application demonstrate the above scenario.
public class ResourceAllocator { private static Object resource = new Object(); private static boolean thread1Wait = false; private static int counter = 1; public static void main(String args[]) { Thread t1 = new Thread() { public void run() { synchronized (resource) { for (int i = 0; i < 10; i++) { if (thread1Wait) { try { resource.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T1, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; resource.notifyAll(); } } } }; Thread t2 = new Thread() { public void run() { synchronized (resource) { for (int i = 0; i < 10; i++) { if (!thread1Wait) { try { resource.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T2, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; resource.notifyAll(); } } } }; t1.start(); t2.start(); } }
Let
me briefly explain above program.
Thread t1 = new Thread() { public void run() { synchronized (resource) { for (int i = 0; i < 10; i++) { if (thread1Wait) { try { resource.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T1, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; resource.notifyAll(); } } } };
Thread1
verifies the flag, thread1Wait, if it is set to true, it release the lock on
the resource by calling wait method. Else it takes the resource and increment
the counter, invert the flag thread1Wait, and notifies other threads that are
waiting for this resource.
ReentrantLock
Anything you can do with synchronized keyword, you can also do with ReentrantLock but not vice-versa. ReentrantLock provides lock and unlock methods to acquire and release the locks.
Anything you can do with synchronized keyword, you can also do with ReentrantLock but not vice-versa. ReentrantLock provides lock and unlock methods to acquire and release the locks.
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.TimeUnit; public class Task implements Runnable{ int taskNum; static Lock myLock = new ReentrantLock(); Task(int taskNum){ this.taskNum = taskNum; } @Override public void run(){ myLock.lock(); try { System.out.println(taskNum +" Started "); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ex) { } System.out.println(taskNum +" Finished "); } finally { myLock.unlock(); } } } public class LockDemo { public static void main(String args[]){ int taskNum = 1; while(taskNum < 50){ Task task = new Task(taskNum); new Thread(task).start(); taskNum++; } } }
Output
1 Started 1 Finished 2 Started 2 Finished 3 Started 3 Finished 4 Started 4 Finished 6 Started 6 Finished 5 Started 5 Finished … … …
Reentrant
class provides following constructors to instantiate.
public
ReentrantLock()
public
ReentrantLock(boolean fair)
The
constructor for this class accepts an optional fairness parameter. When set
true, under contention, locks favor granting access to the longest-waiting
thread. Otherwise this lock does not guarantee any particular access order.
Default constructor sets the fairness to false, to improveperformance.
Following
table summarizes all the methods of ReentrantLock class.
Method
|
Description
|
public
void lock()
|
Acquires
the lock and set the lock count to 1, if this lock is not hold by any other
thread.
If
the lock is already hold by another thread, then current thread becomes disabled for
thread scheduling purposes and lies dormant until the lock has been acquired
If
the current thread already holds the lock, then the lock count is incremented
by 1, method returns immediately.
|
public
void lockInterruptibly() throws InterruptedException
|
It
is same as lock method, but If the lock is held by another thread then the
current thread becomes disabled for thread scheduling purposes and lies
dormant until one of two things happens:
a.
The lock is acquired by the current thread; or
b.
Some other thread interrupts the current thread.
|
public
boolean tryLock()
|
Acquires
the lock only if it is not held by another thread at the time of invocation. It
returns true if the lock was free and was acquired by the current thread, or
the lock was already held by the current thread; and false otherwise
|
public
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
|
Acquires
the lock if it is not held by another thread within the given waiting time
and the current thread has not been interrupted.
If
the lock is held by another thread then the current thread becomes disabled
for thread scheduling purposes and lies dormant until one of three things
happens:
a.
The lock is acquired by the current thread; or
b.
Some other thread interrupts the current thread; or
c.
The specified waiting time elapses
|
public
void unlock()
|
If
the current thread is the holder of this lock then the hold count is
decremented. If the hold count is now zero then the lock is released. If the
current thread is not the holder of this lock then
IllegalMonitorStateException is thrown.
|
public
Condition newCondition()
|
Returns
a Condition instance for use with this Lock instance. Condition object
supports wait, notify and notifyAll functionalities of Object class by
providing await, signal, signalAll methods.
When
the condition waiting methods are called the lock is released and, before
they return, the lock is reacquired and the lock hold count restored to what
it was when the method was called.
|
public
int getHoldCount()
|
Return
the number of holds on this lock by the current thread, or zero if this lock
is not held by the current thread
|
public
boolean isHeldByCurrentThread()
|
Return
true if current thread holds this lock and false otherwise
|
public
boolean isLocked()
|
Return
true if any thread holds this lock and false otherwise
|
public
final boolean isFair()
|
Return
true if this lock has fairness set true
|
public
final boolean hasQueuedThreads()
|
Return
true if there may be other threads waiting to acquire the lock
|
public
final boolean hasQueuedThread(Thread thread)
|
Return
true if the given thread is queued waiting for this lock
|
public
final int getQueueLength()
|
Return
the estimated number of threads waiting for this lock
|
public
boolean hasWaiters(Condition condition)
|
Return
true if there are any waiting threads on the given condition associated with
this lock.
|
public
int getWaitQueueLength(Condition condition)
|
Return
the estimated number of waiting threads on the given condition associated
with this lock
|
In
addition to above public methods, ReentrantLock class provides following
protected methods, sub classes can override these methods on need basis.
Method
|
Description
|
protected
Thread getOwner()
|
Returns
the thread that currently owns this lock, or null if not owned.
|
protected
Collection<Thread> getQueuedThreads()
|
Returns
a collection containing threads that may be waiting to acquire this lock.
|
protected
Collection<Thread> getWaitingThreads(Condition condition)
|
Returns
a collection containing those threads that may be waiting on the given
condition associated with this lock.
|
Now
let’s rewrite our resource allocation problem using ReentrantLock and Condition
object.
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ResourceAllocator { private static Lock reentrantLock = new ReentrantLock(); final static Condition waitCondition = reentrantLock.newCondition(); private static boolean thread1Wait = false; private static int counter = 1; public static void main(String args[]) { Thread t1 = new Thread() { public void run() { try { reentrantLock.lock(); for (int i = 0; i < 10; i++) { if (thread1Wait) { try { waitCondition.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T1, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; waitCondition.signalAll(); } } finally { reentrantLock.unlock(); } } }; Thread t2 = new Thread() { public void run() { try { reentrantLock.lock(); for (int i = 0; i < 10; i++) { if (!thread1Wait) { try { waitCondition.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T2, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; waitCondition.signalAll(); } } finally { reentrantLock.unlock(); } } }; t1.start(); t2.start(); } }
Output
(T1, R1) (T2, R2) (T1, R3) (T2, R4) (T1, R5) (T2, R6) (T1, R7) (T2, R8) (T1, R9) (T2, R10) (T1, R11) (T2, R12) (T1, R13) (T2, R14) (T1, R15) (T2, R16) (T1, R17) (T2, R18) (T1, R19) (T2, R20)
private static Lock
reentrantLock = new ReentrantLock();
Above
statement instantiate ReentrantLock object.
Final static
Condition waitCondition = reentrantLock.newCondition();
Above
statement gets the new condition object on this lock. You can create any number
of condition objects for a lock instance. By using condition object, you can
release the lock by calling await method and notify other threads that are
waiting on given condition objects etc.,
Thread t1 = new Thread() { public void run() { try { reentrantLock.lock(); for (int i = 0; i < 10; i++) { if (thread1Wait) { try { waitCondition.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("(T1, R" + counter + ")"); counter++; thread1Wait = !thread1Wait; waitCondition.signalAll(); } } finally { reentrantLock.unlock(); } } };
Make
sure you call the unlock method in finally block. Since if you don’t call
unlock method on given lock instance, other threads can’t access the shared
data hold by given lock. Since finally block always executes, call unlock
inside finally block.
How the lock hold
count related to lock release?
When
a thread acquires a lock, the acquisition count associated with the lock is set
to 1. If the same thread acquire the lock again, the acquisition count is
incremented and the lock then needs to be released twice to truly release the
lock. Java supports maximum of 2147483647 recursive locks by the same thread.
Attempts to exceed this limit result in Error throws from locking methods.
Why should we release
the lock in finally block?
The lock must be released in finally block, otherwise, if the protected code throws an exception, the lock might never be released. Always follow the following template while working with locks.
The lock must be released in finally block, otherwise, if the protected code throws an exception, the lock might never be released. Always follow the following template while working with locks.
Lock lock = new ReentrantLock(); lock.lock(); try { // Shared Data to work with } finally { lock.unlock(); }
What is fair lock?
A
fair lock is one where the threads acquire the lock in the same order they
asked for it. In unfair lock, a thread can sometimes acquire a lock before
another thread that asked for it first. Unfair locking works efficient than
fair locking, always use unfair locking, unless your application forced you to
use fair locking.
Synchronization Vs
ReentrantLock
a. It is not possible to
interrupt a thread that is waiting to acquire a lock, whereas by using
ReentrantLock, you can wait for some period of time to acquire a lock, if
thread is unable to get the lock in given period of time, it can come out.
b. The lock must be
released in finally block, otherwise, if the protected code throws an
exception, the lock might never be released. You must takes care of releasing
the locks properly. In case of synchronization, JVM takes care of releasing the
locks.
c. A ReentrantLock is
unstructured, unlike synchronized constructs -- i.e. you don't need to use a
block structure for locking and can even hold a lock across methods.
Ex:
private ReentrantLock lock = new ReentrantLock(); public void foo() { ... lock.lock(); ... } public void bar() { ... lock.unlock(); ... }
d. Under high
contention, Reentrant lock is better than synchronized construct. Go through
following article for more information.
e. You can get list of
threads that are waiting on given lock, it is not possible using
synchronization.
f. ReentrantLock provide
method to check whether a lock is being hold by any thread or not.
g. By using
ReentrantLock, you can try for a lock without blocking. But same is not
possible in synchronization.
You may like
No comments:
Post a Comment