I'm trying to understand what makes the lock in concurrency so important if one can use synchronized (this)
. In the dummy code below, I can do either:
- synchronized the entire method or synchronize the vulnerable area (synchronized(this){...})
- OR lock the vulnerable code area with a ReentrantLock .
Code:
private final ReentrantLock lock = new ReentrantLock();
private static List<Integer> ints;
public Integer getResult(String name) {
.
.
.
lock.lock();
try {
if (ints.size()==3) {
ints=null;
return -9;
}
for (int x=0; x<ints.size(); x++) {
System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
}
} finally {
lock.unlock();
}
return random;
}
Lets assume this code is running in a thread:
Because the thread owns the lock it will allow multiple calls to lock(), so it re-enter the lock. This can be achieved with a reference count so it doesn't has to acquire lock again.
ReentrantReadWriteLock
is a specialized lock whereassynchronized(this)
is a general purpose lock. They are similar but not quite the same.You are right in that you could use
synchronized(this)
instead ofReentrantReadWriteLock
but the opposite is not always true.If you'd like to better understand what makes
ReentrantReadWriteLock
special look up some information about producer-consumer thread synchronization.In general you can remember that whole-method synchronization and general purpose synchronization (using the
synchronized
keyword) can be used in most applications without thinking too much about the semantics of the synchronization but if you need to squeeze performance out of your code you may need to explore other more fine-grained, or special-purpose synchronization mechanisms.By the way, using
synchronized(this
) - and in general locking using a public class instance - can be problematic because it opens up your code to potential dead-locks because somebody else not knowingly might try to lock against your object somewhere else in the program.Synchronized locks does not offer any mechanism of waiting queue in which after the execution of one thread any thread running in parallel can acquire the lock. Due to which the thread which is there in the system and running for a longer period of time never gets chance to access the shared resource thus leading to starvation.
Reentrant locks are very much flexible and has a fairness policy in which if a thread is waiting for a longer time and after the completion of the currently executing thread we can make sure that the longer waiting thread gets the chance of accessing the shared resource hereby decreasing the throughput of the system and making it more time consuming.
You can use reentrant locks with a fairness policy or timeout to avoid thread starvation. You can apply a thread fairness policy. it will help avoid a thread waiting forever to get to your resources.
The "fairness policy" picks the next runnable thread to execute. It is based on priority, time since last run, blah blah
also, Synchronize can block indefinitely if it cant escape the block. Reentrantlock can have timeout set.
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. An example:Such flow is impossible to represent via a single monitor in a
synchronized
construct.Aside from that,
ReentrantLock
supports lock polling and interruptible lock waits that support time-out.ReentrantLock
also has support for configurable fairness policy, allowing more flexible thread scheduling.ReentrantLock
may also be more scalable, performing much better under higher contention. You can read more about this here.This claim has been contested, however; see the following comment:
When should you use
ReentrantLock
s? According to that developerWorks article...From oracle documentation page about ReentrantLock:
A ReentrantLock is owned by the thread last successfully locking, but not yet unlocking it. A thread invoking lock will return, successfully acquiring the lock, when the lock is not owned by another thread. The method will return immediately if the current thread already owns the lock.
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.
ReentrantLock key features as per this article
You can use ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock to further acquire control on granular locking on read and write operations.
Have a look at this article by Benjamen on usage of different type of ReentrantLocks