In Java 5.0, a new addition called
Reentrant Lock was made to enhance intrinsic locking capabilities. Prior to
this, "synchronized" and "volatile" were the means for
achieving concurrency.
public synchronized void doAtomicTransfer(){
//enter
synchronized block , acquire lock over this object.
operation1()
operation2();
}
Synchronized uses intrinsic locks or
monitors. Every object in Java has an intrinsic lock associated with it.
Whenever a thread tries to access a synchronized block or method, it acquires
the intrinsic lock or the monitor on that object.
In case of static methods, the thread
acquires the lock over the class object. An intrinsic locking mechanism is a
clean approach in terms of writing code, and is pretty good for most of the
use-cases.
An intrinsic locking mechanism can have
some functional limitations, such as:
·
It
is not possible to interrupt a thread waiting to acquire a lock (lock
Interruptibly).
·
It
is not possible to attempt to acquire a lock without being willing to wait for
it forever (try lock).
·
Cannot
implement non-block-structured locking disciplines, as intrinsic locks must be
released in the same block in which they are acquired.
Lets see a few of the methods implemented
by ReentrantLock class (which implements Lock):
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
Lets try and understand the use of these
and see what benefits we can get.
1. Polled and Timed Lock Acquisition
Let's see some example code:
public void transferMoneyWithSync(Account fromAccount, Account toAccount, float amount) throws InsufficientAmountException {
synchronized (fromAccount) {
// acquired lock on fromAccount Object
synchronized (toAccount) {
// acquired lock on toAccount Object
if (amount >
fromAccount.getCurrentAmount()) {
throw new InsufficientAmountException("Insufficient
Balance");
} else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
In the transferMoney() method above,
there is a possibility of deadlock when two threads A and B are trying to transfer
money at almost the same time.
A: transferMoney(acc1, acc2, 20);
B:
transferMoney(acc2, acc1 ,25);
It is possible that thread A has acquired
a lock on the acc1 object and is waiting to acquire a lock on the acc2 object.
Meanwhile, thread B has acquired a lock on the acc2 object and is waiting for a
lock on acc1. This will lead to deadlock, and the system would have to be restarted!
There is, however, a way to avoid this, which is called "lock
ordering." Personally, I find this a bit complex.
A cleaner approach is implemented by
ReentrantLock with the use of tryLock() method. This approach is called the
"timed and polled lock-acquisition." It lets you regain control if
you cannot acquire all the required locks, release the ones you have acquired
and retry. So, using tryLock, we will attempt to acquire both locks. If we
cannot attain both, we will release if one of these has been acquired, then retry.
public boolean transferMoneyWithTryLock(Account fromAccount, Account toAccount, float amount) throws InsufficientAmountException,
InterruptedException {
// we are defining a stopTime
long stopTime = System.nanoTime() +
5000;
while (true) {
if (fromAccount.lock.tryLock()) {
try {
if (toAccount.lock.tryLock()) {
try {
if (amount >
fromAccount.getCurrentAmount()) {
throw new InsufficientAmountException ("Insufficient
Balance");
}
else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
finally {
toAccount.lock.unlock();
}
}
} finally {
fromAccount.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
Thread.sleep(100);
} // while
}
Here we implemented a timed lock, so if
the locks cannot be acquired within the specified time, the transferMoney
method will return a failure notice and exit gracefully. We can also maintain
time budget activities using this concept.
2. Interruptible Lock Acquisition
Interruptible lock acquisition allows
locking to be used within cancellable activities.
The lockInterruptibly() method allows us
to try and acquire a lock while being available for interruption. So, basically
it allows the thread to immediately react to the interrupt signal sent to it
from another thread.
This can be helpful when we want to send
a KILL signal to all the waiting locks.
Let's see one example: Suppose we have a
shared line to send messages. We would want to design it in such a way that if
another thread comes and interrupts the current thread, the lock should release
and perform the exit or shut down operations to cancel the current task.
public boolean sendOnSharedLine(String message) throws InterruptedException {
lock.lockInterruptibly();
try {
return
cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean
cancellableSendOnSharedLine(String message){
.......
The timed tryLock is also responsive to
interruption.
3. Non-block Structured Locking:
In intrinsic locks, acquire-release pairs
are block-structured.
In other words, a lock is always released
in the same basic block in which it was acquired, regardless of how control
exits the block.
Extrinsic locks allow the facility to
have more explicit control. Some concepts, like Lock Strapping, can be achieved
more easily using extrinsic locks. Some use cases are seen in hash-bashed
collections and linked lists.
4. Fairness:
The ReentrantLock constructor offers a
choice of two fairness options: create a non-fair lock or a fair lock. With
fair locking, threads can acquire locks only in the order in which they were
requested, whereas an unfair lock allows a lock to acquire it out of its turn.
This is called barging (breaking the queue and acquiring the lock when it
became available).
Fair locking has a significant
performance cost because of the overhead of suspending and resuming threads.
There could be cases where there is a significant delay between when a
suspended thread is resumed and when it actually runs. Let's see a situation:
A
-> holds a lock.
B -> has requested and is in a suspended state waiting for
A to release the lock.
C
-> requests the lock at the same time that A releases the lock, and has not
yet gone to a suspended state.
As C has not yet gone to a suspended
state, there is a chance that it can acquire the lock released by A, use it,
and release it before B even finishes waking up. So, in this context, unfair
lock has a significant performance advantage.
Intrinsic locks and extrinsic locks have
the same mechanism inside for locking, so the performance improvement is purely
subjective. It depends on the use cases we discussed above. Extrinsic locks
give a more explicit control mechanism for better handling of deadlocks,
starvation, and so on.
import java.io.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Java program to show,
how to use ReentrantLock in Java. Reentrant lock is an
* alternative way of
locking apart from implicit locking provided by
* synchronized keyword in
Java.
*/
public class ReentrantLock {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
// Locking using Lock and ReentrantLock
public int getCount() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()
+
" gets Count: " + count);
return count++;
} finally {
lock.unlock();
}
}
// Implicit locking using synchronized keyword
public synchronized int getCountTwo() {
return count++;
}
public static void main(String args[]) {
final ThreadTest counter = new ThreadTest();
Thread t1 = new Thread() {
@Override
public void run() {
while (counter.getCount() < 6) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
while (counter.getCount() < 6) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
};
t1.start();
t2.start();
public void run() {
while (counter.getCount() < 6) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}};
t1.start();t2.start();
}
}
Output:Thread-0
gets Count:0 Thread-1
gets Count:1 Thread-1
gets Count:2 Thread-0
gets Count:3 Thread-1
gets Count:4 Thread-0
gets Count:5 Thread-0
gets Count:6 Thread-1
gets
Count:7
We recommend you take Big Data Hadoop class room training at eMexo Technologies in electronic city, Bangalore to learn more about Big Data Hadoop.
0 Comments:
Post a Comment