Synchronization vs Lock
Asked Answered
C

11

211

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?

Codfish answered 17/11, 2010 at 5:13 Comment(1)
helpful article here : javarevisited.blogspot.in/2013/03/…Horologium
S
209

If you're simply locking an object, I'd prefer to use synchronized

Example:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

You have to explicitly do try{} finally{} everywhere.

Whereas with synchronized, it's super clear and impossible to get wrong:

synchronized(myObject) {
    doSomethingNifty();
}

That said, Locks may be more useful for more complicated things where you can't acquire and release in such a clean manner. I would honestly prefer to avoid using bare Locks in the first place, and just go with a more sophisticated concurrency control such as a CyclicBarrier or a LinkedBlockingQueue, if they meet your needs.

I've never had a reason to use wait() or notify() but there may be some good ones.

Strobe answered 17/11, 2010 at 5:18 Comment(3)
What's the difference between wait/notify vs park/unpark of LockSupport? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…Sputter
At first the example made sense with locks but then I realized that if you use a try finally block that problem would be avoided with locks not being releasedAsiaasian
Ahh... One of those moments to appreciate RAII model in C++. std::lock_guardVedavedalia
B
82

I am wondering which one of these is better in practice and why?

I've found that Lock and Condition (and other new concurrent classes) are just more tools for the toolbox. I could do most everything I needed with my old claw hammer (the synchronized keyword), but it was awkward to use in some situations. Several of those awkward situations became much simpler once I added more tools to my toolbox: a rubber mallet, a ball-peen hammer, a prybar, and some nail punches. However, my old claw hammer still sees its share of use.

I don't think one is really "better" than the other, but rather each is a better fit for different problems. In a nutshell, the simple model and scope-oriented nature of synchronized helps protect me from bugs in my code, but those same advantages are sometimes hindrances in more complex scenarios. Its these more complex scenarios that the concurrent package was created to help address. But using this higher level constructs requires more explicit and careful management in the code.

===

I think the JavaDoc does a good job of describing the distinction between Lock and synchronized (the emphasis is mine):

Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.

...

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: when multiple locks are acquired they must be released in the opposite order, and all locks must be released in the same lexical scope in which they were acquired.

While the scoping mechanism for synchronized methods and statements makes it much easier to program with monitor locks, and helps avoid many common programming errors involving locks, there are occasions where you need to work with locks in a more flexible way. For example, **some algorithms* for traversing concurrently accessed data structures require the use of "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on. Implementations of the Lock interface enable the use of such techniques by allowing a lock to be acquired and released in different scopes, and allowing multiple locks to be acquired and released in any order.

With this increased flexibility comes additional responsibility. The absence of block-structured locking removes the automatic release of locks that occurs with synchronized methods and statements. In most cases, the following idiom should be used:

...

When locking and unlocking occur in different scopes, care must be taken to ensure that all code that is executed while the lock is held is protected by try-finally or try-catch to ensure that the lock is released when necessary.

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 can timeout (tryLock(long, TimeUnit)).

...

Bergess answered 17/11, 2010 at 6:29 Comment(0)
S
30

You can achieve everything the utilities in java.util.concurrent do with the low-level primitives like synchronized, volatile, or wait / notify

However, 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).

Sweetsop answered 17/11, 2010 at 5:18 Comment(7)
Is plain old wait/notify considered a lower-level primitive than java.util.concurrent.locks.LockSupport's park/unpark, or is it the other way round?Sputter
@Pacerier: I consider both to be low-level (i.e. something that an application programmer would want to avoid using directly), but certainly the lower-level parts of java.util.concurrency (such as the locks package) are built on top of the native JVM primitives wait/notify (which are even lower-level).Sweetsop
No I mean out of the 3: Thread.sleep/interrupt, Object.wait/notify, LockSupport.park/unpark, which is the most primitive?Sputter
@Pacerier: Maybe open a new question for that. Whether any of them is implemented on top of the other, if they all share the same even lower-level JVM internal primitives, or if they are independent from eachother.Sweetsop
@Sweetsop I'm not sure how you support your statement that java.util.concurrent is easier [in general] than the language features (synchronized, etc...). When you use java.util.concurrent you have to make a habit of completing lock.lock(); try { ... } finally { lock.unlock() } before writing code whereas with a synchronized you are basically fine from the start. On this basis alone I would say synchronized is easier (given you want its behavior) than java.util.concurrent.locks.Lock. par 4Rufena
@Iwan: That's what I meant by "the Lock class itself is on the lower-level side of this toolbox, you may not even need to use that directly either". The easy/safe/convenient tools are Queues and Futures and CompletionService and so on.Sweetsop
Don't think you can exactly replicate the behavior of the AtomicXXX classes with just the concurrency primitives, as they rely on native CAS invocation not available prior to java.util.concurrent.Rock
T
19

There are 4 main factors into why you would want to use synchronized or java.util.concurrent.Lock.

Note: Synchronized locking is what I mean when I say intrinsic locking.

  1. 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.

  2. 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

  3. Style. If both 1 and 2 do not fall into categories of what you are concerned with most people, including myself, would find the intrinsic locking semenatics easier to read and less verbose then j.u.c.Lock locking.
  4. Multiple Conditions. An object you lock on can only be notified and waited for a single case. Lock's newCondition method allows for a single Lock to have mutliple reasons to await or signal. I have yet to actually need this functionality in practice, but is a nice feature for those who need it.
Transplant answered 17/11, 2010 at 14:43 Comment(2)
I liked the details on your comment. I would add one more bullet point - the ReadWriteLock provides useful behavior if you are dealing with several threads, only some of which need to write to the object. Multiple threads can be concurrently reading the object and are only blocked if another thread is already writing to it.Collette
To add to the 4th point - In j.u.c.ArrayBlockingQueue, the lock has 2 reasons to await: queue non-empty and queue non-full. For this reason, j.u.c.ArrayBlockingQueue uses the explicit lock and lock.newCondition().Macerate
M
6

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

synchronized(foo) {
}

or

lock.acquire(); .....lock.release();

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.

Malayopolynesian answered 23/9, 2011 at 18:6 Comment(5)
"Method level synchronization ensures fair or FIFO allocation of the lock." => Really? Are you saying that a synchronized method behaves differently w.r.t. fairness than wrapping the methods content into a synchronized{} block? I wouldn't think so, or did I understand that sentence wrong...?Cab
Yes, although surprising and counter intuitive thats correct. Goetz's book is the best explanation.Malayopolynesian
If you look at the code provided by @BrianTarbox the synchronized block is using some object other than "this" to lock. In theory there's no difference between a synchronized method and putting the entire body of said method inside a synchronized block, as long as the block uses "this" as lock.Kyte
Answer should be edited to include quote, and make it clear " assurance" here is "statistical guarantee", not deterministic.Halsey
The lock could be fair. See the constructor for ReentrantLockPeplum
H
6

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)

A Lock provides exclusive access to a shared resource: only one thread at a time can acquire the lock and all access to the shared resource requires that the lock be acquired first. However, some locks may allow concurrent access to a shared resource, such as the read lock of a ReadWriteLock.

Advantages of Lock over Synchronization from documentation page

  1. 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

  2. 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 can timeout (tryLock(long, TimeUnit)).

  3. 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 article

  1. Ability to lock interruptibly.
  2. Ability to timeout while waiting for lock.
  3. Power to create fair lock.
  4. API to get list of waiting thread for lock.
  5. Flexibility to try for lock without blocking.

You 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:

Java 8 ships with a new kind of lock called StampedLock which also support read and write locks just like in the example above. In contrast to ReadWriteLock the locking methods of a StampedLock return a stamp represented by a long value.

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 and StampedLock locks.

Hintz answered 14/12, 2015 at 12:59 Comment(0)
P
6

Major difference between lock and synchronized:

  • with locks, you can release and acquire the locks in any order.
  • with synchronized, you can release the locks only in the order it was acquired.
Prague answered 30/8, 2017 at 14:26 Comment(0)
R
4

Lock makes programmers' life easier. Here are a few situations that can be achieved easily with lock.

  1. Lock in one method, and release the lock in another method.
  2. If You have two threads working on two different pieces of code, however, in the first thread has a pre-requisite on a certain piece of code in the second thread (while some other threads also working on the same piece of code in the second thread simultaneously). A shared lock can solve this problem quite easily.
  3. Implementing monitors. For example, a simple queue where the put and get methods are executed from many other threads. However, you do not want multiple put (or get) methods running simultaneously, neither the put and get method running simultaneously. A private lock makes your life a lot easier to achieve this.

While, the lock, and conditions build on the synchronized mechanism. Therefore, can certainly be able to achieve the same functionality that you can achieve using the lock. However, solving complex scenarios with synchronized may make your life difficult and can deviate you from solving the actual problem.

Rodmun answered 16/6, 2016 at 1:9 Comment(0)
J
3

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..."

Jalousie answered 23/3, 2012 at 13:50 Comment(0)
A
1

Here is comprehensive comparison synchronized vs Lock

Synchronized advantages:

  • Automatic release: no need in try-finally, protection against forgotten release.

     synchronized {
         someCode();
     }
    

    vs

     lock.lock();
     try {
         someCode();
     } finally {
         lock.release();
     }
    
  • Less extra lines (an example above)

  • Ability to mark an entire method synchronized, avoid extra lines and extra tabulation

     public synchronized someMethod() {
         someCode();
     }
    

Lock advantages:

  • Extended API: .lockInterruptibly(), .tryLock(), tryLock(timeout)
  • Ability to create multiple Conditions associated with a lock. For example, ArrayBlockingQueue holds both notEmpty and notFull conditions.
  • Ability to get lock-holding thread while debugging via sync.exclusiveOwnerThread. API for obtaining Lock state: .getHoldCount(), .hasQueuedThreads() etc.
  • Ability to pass as an object. For example keep a Map<ResourceObject, Lock>
  • Ability to acquire and release multiple locks in any order
  • Ability to create fair: new ReentrantLock(true)
  • Existence of ReadWriteLock allowing parallel reads
Amoritta answered 15/7, 2023 at 23:50 Comment(0)
A
0

Lock and synchronize block both serves the same purpose but it depends on the usage. Consider the below part

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

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

Aargau answered 27/7, 2018 at 6:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.