Synchronized Block within Synchronized Method
Asked Answered
B

4

8

I'm looking at some code in a third party library that contains a synchronized method, and within this method there is a synchronized block that locks on an instance variable. It's similar to this:

public class Foo {
   final Bar bar = new Bar();

   public synchronized void doSomething() {
       // do something
       synchronized(bar) {
           // update bar
       }
   }
   ...
}

Does this make sense? If so, what benefits are there to having a synchronized statement within a synchronized method?

Given that a synchronized method locks on the entire object, it seems redundant to me. Perhaps this approach makes sense when working with instance variables that are not private?

Bandur answered 1/9, 2015 at 18:57 Comment(1)
What I believe is , if the monitor object was "this" instead of "bar" for the synchronized block, then it would have been redundant.Dollfuss
N
12

In your example the method is both locking on the instance of Foo and on the object bar. Other methods may only be locking on the instance of Foo or on the object bar.

So, yes, this makes sense depending on exactly what they are doing. Presumably bar protects some smaller subset of data, and some methods will only need to lock on bar to perform their actions in a thread-safe manner.

What synchronized does

synchronized (on a method, or in a statement) creates a mutual exclusion zone (critical section or, specifically for Java, a reentrant mutex). The "key" for a thread to enter the critical section is the object reference used in the synchronized statement . Only one thread can (recursively) "own" this "key" at one time across all blocks that use the same key; that is, only one thread can enter any block synchronized on a given object reference at one time.

Such a critical section simply prevents the operations (variable read/write) that you do inside the block happening concurrently with any other operations in all other critical sections that lock on the same object reference. (It doesn't automatically protect all variables inside an object).

In Java, such a critical section also creates a happens-before contract.

By example

As a somewhat contrived example :

public class Foo {
    final Bar bar = new Bar();
    
    private int instanceCounter = 0;
    private int barCounter = 0;

    public synchronized void incrementBarCounterIfAllowed() {
        synchronized (bar) {
            if (instanceCounter < 10) barCounter++;
        }
    }

    public synchronized void incrementClassCounter() {
        instanceCounter++;
    }

    public void incrementBarCounter() {
        synchronized (bar) {
            barCounter++;
        }
    }

}

Whether the instance variables are private or not doesn't really matter to whether this approach is applicable. In a single class you can have multiple lock objects, each of which protect their own set of data.

However the risk of doing this is that you have to be very strict with coding conventions to prevent deadlocks by locking two locks in different orders in different places. For example, with the above code if you then do this from somewhere else in the code:

synchronized(myFoo.bar) {
  myFoo.incrementClassCounter();
}

you risk a deadlock with the incrementBarCounterIfAllowed() method

Note that barCounter could be an instance variable for Bar etc etc - I avoided that for the sake of brevity in the code sample.

In the case of synchronized methods, that reference is the reference to the class instance (or to the Class<?> for the class for static methods).

Necromancy answered 1/9, 2015 at 18:59 Comment(4)
Thx for the reply. As you said, each lock protects their own set of data. I was thinking that when an object is locked, then all of its instance variables would then be protected as well. I take it that this is not the case?Bandur
@TheGilbertArenasDagger - hopefully my updated answer addresses that question. The lock is a mutual-exclusion zone coordinated on the reference to the object passed to synchronize. It protects whatever you choose to do inside the lock. It is up to you do consistently protect the same data with the same lock. e.g. public void breakMe() { instanceCounter++; } could be added to the above sample and instanceCounter would no longer be thread-safe.Necromancy
@TheGilbertArenasDagger Yes - unless some external class decided it wanted to do concurrent operations on that instance of bar, which would clash with the operations you did inside Foo (that would be a pretty bad concurrent design though).Necromancy
@TheGilbertArenasDagger, "locking" an object (i.e., synchronizing on it) does not prevent other threads from modifying the object. It only prevents other threads from "locking" the same object at the same time.Hasin
D
2

When you say "synchronized method locks on the entire object", that's not true. Using synchronized only means that threads have to acquire that lock before they can enter the methods or blocks marked as synchronized that use that lock. The default object used as a lock for synchronized on instance methods is this. The default is this.getClass() if you put synchronized on a static method, or you can specify the object to use as a lock. Using synchronized doesn't do anything other than that to make instance fields inaccessible.

You can write a class where some methods or blocks are protected by one lock, some are protected by another lock, and for others you need both locks. Make sure you acquire the locks in the same order or you can cause a deadlock.

Dunham answered 1/9, 2015 at 19:15 Comment(0)
D
1

Consider this:

public void someMethod() {

    synchronized(bar) {
       // fully accessible before entering the other synchronized bar block
       // but not afterwards 
    }

}

Get it clearly, synchronizing only blocks if 2 blocks synchronize on the same object.

Depraved answered 1/9, 2015 at 19:6 Comment(1)
Actually, if you have two threads, they will not coexist in the synchronized(bar) block at the same time, so a single "synchronized(bar)" block will block other threads on the same block too. Also, in addition to your statement, "synchronzied(bar)" is fully accessible after a thread has left the other "synchonized(bar)" block, while your comment could be interpreted to mean it remains inaccessible "forever" afterwards.Conference
H
1

I will give a real life example to explain what Andy explained through code(to those who are finding it difficult to understand this):

Suppose You have a 1 BHK Flat .

  1. The way to enter in room is through hall (you have to first enter into hall to enter into room)
  2. You want to restrict some one to use you room , but he can use/enter in the hall.

In this case if someone enters into room from back door and applies lock from inside. One can only enter into the hall and cant enter into the room until the person inside the room releases the lock .

Hope this clarifies to the people who are finding it difficult to understand this.

Homebody answered 5/4, 2016 at 12:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.