java.util.concurrent
API provides a class called as Lock
, which would basically serialize the control in order to access the critical resource. It gives method such as park()
and unpark()
.
We can do similar things if we can use synchronized
keyword and using wait()
and notify() notifyAll()
methods.
I am wondering which one of these is better in practice and why?
You can achieve everything the utilities in java.util.concurrent do with the low-level primitives like
synchronized
,volatile
, or wait / notifyHowever, concurrency is tricky, and most people get at least some parts of it wrong, making their code either incorrect or inefficient (or both).
The concurrent API provides a higher-level approach, which is easier (and as such safer) to use. In a nutshell, you should not need to use
synchronized, volatile, wait, notify
directly anymore.The Lock class itself is on the lower-level side of this toolbox, you may not even need to use that directly either (you can use
Queues
and Semaphore and stuff, etc, most of the time).There are 4 main factors into why you would want to use
synchronized
orjava.util.concurrent.Lock
.Note: Synchronized locking is what I mean when I say intrinsic locking.
When Java 5 came out with ReentrantLocks, they proved to have quite a noticeble throughput difference then intrinsic locking. If youre looking for faster locking mechanism and are running 1.5 consider j.u.c.ReentrantLock. Java 6's intrinsic locking is now comparable.
j.u.c.Lock has different mechanisms for locking. Lock interruptable - attempt to lock until the locking thread is interrupted; timed lock - attempt to lock for a certain amount of time and give up if you do not succeed; tryLock - attempt to lock, if some other thread is holding the lock give up. This all is included aside from the simple lock. Intrinsic locking only offers simple locking
I would like to add some more things on top of Bert F answer.
Locks
support various methods for finer grained lock control, which are more expressive than implicit monitors (synchronized
locks)Advantages of Lock over Synchronization from documentation page
The use of synchronized methods or statements provides access to the implicit monitor lock associated with every object, but forces all lock acquisition and release to occur in a block-structured way
Lock implementations provide additional functionality over the use of synchronized methods and statements by providing a non-blocking attempt to acquire a
lock (tryLock())
, an attempt to acquire the lock that can be interrupted (lockInterruptibly()
, and an attempt to acquire the lock that cantimeout (tryLock(long, TimeUnit))
.A Lock class can also provide behavior and semantics that is quite different from that of the implicit monitor lock, such as guaranteed ordering, non-reentrant usage, or deadlock detection
ReentrantLock: In simple terms as per my understanding,
ReentrantLock
allows an object to re-enter from one critical section to other critical section . Since you already have lock to enter one critical section, you can other critical section on same object by using current lock.ReentrantLock
key features as per this articleYou can use
ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
to further acquire control on granular locking on read and write operations.Apart from these three ReentrantLocks, java 8 provides one more Lock
StampedLock:
You can use these stamps to either release a lock or to check if the lock is still valid. Additionally stamped locks support another lock mode called optimistic locking.
Have a look at this article on usage of different type of
ReentrantLock
andStampedLock
locks.Lock and synchronize block both serves the same purpose but it depends on the usage. Consider the below part
In the above case , if a thread enters the synchronize block, the other block is also locked. If there are multiple such synchronize block on the same object, all the blocks are locked. In such situations , java.util.concurrent.Lock can be used to prevent unwanted locking of blocks
Brian Goetz's "Java Concurrency In Practice" book, section 13.3: "...Like the default ReentrantLock, intrinsic locking offers no deterministic fairness guarantees, but the statistical fairness guarantees of most locking implementations are good enough for almost all situations..."
The main difference is fairness, in other words are requests handled FIFO or can there be barging? Method level synchronization ensures fair or FIFO allocation of the lock. Using
or
does not assure fairness.
If you have lots of contention for the lock you can easily encounter barging where newer requests get the lock and older requests get stuck. I've seen cases where 200 threads arrive in short order for a lock and the 2nd one to arrive got processed last. This is ok for some applications but for others it's deadly.
See Brian Goetz's "Java Concurrency In Practice" book, section 13.3 for a full discussion of this topic.