Why use a ReentrantLock if one can use synchronized(this)?
Asked Answered
B

8

388

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:

  1. synchronized the entire method or synchronize the vulnerable area (synchronized(this){...})
  2. 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;
}
Bah answered 6/8, 2012 at 2:8 Comment(3)
BTW all java intrinsic locks are reentrant by nature.Cutlass
@pongapundit so synchronized(this){synchronized(this){//some code}} will not cause dead lock. For intrinsic lock if they obtain monitor on a resource and if they want it again they can get it without a dead lock.Cutlass
object.lock;......;object.unlock equals synchronized(this.class) it is at class-level lock not object-levelAlmonry
L
560

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:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

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.

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. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation. Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock. Also note that the untimed tryLock method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting.


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:

In the reentrant lock test, a new lock is created each time, thus there is no exclusive locking and the resulting data is invalid. Also, the IBM link offers no source code for the underlying benchmark so its impossible to characterize whether the test was even conducted correctly.


When should you use ReentrantLocks? According to that developerWorks article...

The answer is pretty simple -- use it when you actually need something it provides that synchronized doesn't, like timed lock waits, interruptible lock waits, non-block-structured locks, multiple condition variables, or lock polling. ReentrantLock also has scalability benefits, and you should use it if you actually have a situation that exhibits high contention, but remember that the vast majority of synchronized blocks hardly ever exhibit any contention, let alone high contention. I would advise developing with synchronization until synchronization has proven to be inadequate, rather than simply assuming "the performance will be better" if you use ReentrantLock. Remember, these are advanced tools for advanced users. (And truly advanced users tend to prefer the simplest tools they can find until they're convinced the simple tools are inadequate.) As always, make it right first, and then worry about whether or not you have to make it faster.


One final aspect that's gonna become more relevant in the near future has to do with Java 15 and Project Loom. In the (new) world of virtual threads, the underlying scheduler would be able to work much better with ReentrantLock than it's able to do with synchronized, that's true at least in the initial Java 15 release but may be optimized later.

In the current Loom implementation, a virtual thread can be pinned in two situations: when there is a native frame on the stack — when Java code calls into native code (JNI) that then calls back into Java — and when inside a synchronized block or method. In those cases, blocking the virtual thread will block the physical thread that carries it. Once the native call completes or the monitor released (the synchronized block/method is exited) the thread is unpinned.

If you have a common I/O operation guarded by a synchronized, replace the monitor with a ReentrantLock to let your application benefit fully from Loom’s scalability boost even before we fix pinning by monitors (or, better yet, use the higher-performance StampedLock if you can).

Lipoid answered 6/8, 2012 at 2:29 Comment(11)
The 'known to be more scalable' link to lycog.com should be removed. In the reentrant lock test, a new lock is created each time, thus there is no exclusive locking and the resulting data is invalid. Also, the IBM link offers no source code for the underlying benchmark so its impossible to characterize whether the test was even conducted correctly. Personally, I'd just remove the whole line about scalability, as the entire claim is essentially unsupported.Electrophorus
I modified the post in light of your response.Lipoid
We can achieve timed locking using wait() also, so can't we apply ReentrantLock functionality in it?Geerts
If the performance is a high concern for you, don't forget to look for a way where you need NO synchronization at all.Hyams
Worth mentioning about locking vs synchronization: mechanical-sympathy.blogspot.com/2011/11/…Media
Regarding benchmarking ReentrantLock vs synchronized, see here: david-soroko.blogspot.co.uk/2016/02/….Spinescent
More generally, Lock objects can be implemented (or overridden) with custom logic, e.g. read/write, distributed locking etc.Johnstone
The performance thing doesn't make sense to me at all. If reantrant lock would perform better then why wouldn't synchronzied not just be implemented the same way a reantrant lock internatlly?Heterotaxis
I'm confused with the sentence, "In the reentrant lock test, a new lock is created each time, thus there is no exclusive locking and the resulting data is invalid". If the resulting data is invalid, why do we use ReentrantLock? The reason to use ReentrantLock or synchronized is having valid data which is shared by many threads. Can someone clarify this? Maybe I misunderstand something.Larocca
@Larocca the ReentrantLockPseudoRandom code in the Lycog link is using brand new, uncontended locks every invocation of setSeed and nextLipoid
Can we write our own Lock implementation by implementing it and providing definition to all its methodCailly
R
16

ReentrantReadWriteLock is a specialized lock whereas synchronized(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 of ReentrantReadWriteLock 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.

Rebato answered 6/8, 2012 at 2:10 Comment(1)
to prevent potential dead-locks because somebody else not knowingly might try to lock against your object somewhere else in the progame use a private Object instance as synchronization Monitor like this: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }Carri
S
14

A simple example using ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public void safeMethod1() {
        try {
            lock.lock();
            /* your business logic goes here */

        } finally {
            lock.unlock();
        }
     }
}

From oracle documentation page about ReentrantLock:

A reentrant mutual exclusion Lock with the same basic behaviour and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.

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 APIs, which are better over synchronized

  1. lockInterruptibly() : Acquires the lock unless the current thread is interrupted.
  2. tryLock() : Acquires the lock only if it is not held by another thread at the time of invocation
  3. tryLock(long timeout, TimeUnit unit) Acquires the lock if it is not held by another thread within the given waiting time and the current thread has not been interrupted. This API avoid thread starvation, which is a drawback with synchronized
  4. public ReentrantLock(boolean fair) : ReentrantLock with the fairness policy.
Sheaff answered 24/4, 2016 at 18:29 Comment(0)
M
6

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.

Myosotis answered 6/10, 2018 at 12:34 Comment(0)
H
5

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.

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

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.

Heeled answered 18/6, 2016 at 18:59 Comment(0)
S
3

One thing to keep in mind is :

The name 'ReentrantLock' gives out a wrong message about other locking mechanism that they are not re-entrant. This is not true. Lock acquired via 'synchronized' is also re-entrant in Java.

Key difference is that 'synchronized' uses intrinsic lock ( one that every Object has ) while Lock API doesn't.

Saccharide answered 22/5, 2019 at 5:15 Comment(0)
O
0

I think the wait/notify/notifyAll methods don't belong on the Object class as it pollutes all objects with methods that are rarely used. They make much more sense on a dedicated Lock class. So from this point of view, perhaps it's better to use a tool that is explicitly designed for the job at hand - ie ReentrantLock.

Obumbrate answered 16/4, 2020 at 1:2 Comment(0)
T
-1

Lets assume this code is running in a thread:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

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.

Thanatopsis answered 14/11, 2018 at 7:39 Comment(1)
A synchronized block has exactly the same reentrance behavior (reference counting). That is not one of the advantages/features of ReentrantLock.Amphimixis

© 2022 - 2024 — McMap. All rights reserved.