Why would I ever create a separate mutex/lock object?

81 Views Asked by At

I am modifying some concurrent code at work and recently read through Java's docs on Intrinsic Locks and Synchronization.

Given that every object has an intrinsic lock, why would I ever create a separate object for the purposes of controlling access to a particular item?

I recognize that there may be a use case where the item of interest is not a malloc'd Object, (int vs Integer) and therefore doesn't have an intrinsic lock but... assuming that we are concerned about synchronizing some static Object, is there anything to lose?

e.g.:

public class main {
    public static void main (String[] args){
        Integer foo = 10;
        
        synchronized(foo){
            foo ++;
        }
    }
}

If I wanted to update foo from multiple threads, synchronously, why wouldn't I just use the object that I want to modify? Is this less performant? I see lots of synchronized(this), and separate instances where we may make a lock object for the purposes of synchronization:

public class main {
    public static void main (String[] args){
        Integer foo = 10; 
        Object fooLock = new Object();
        
        synchronized(fooLock){
            foo ++;
        }
    }
}

Why would I ever create fooLock, when I could instead use the object of actual interest? Is this actually discouraged (not idiomatic), or is there a practical reason not to do this?

I am thinking of doing the first approach (synchronized(foo)) for a static socket connection object, but it concerns me that I have not seen discussion of this. Am I missing something?

3

There are 3 best solutions below

2
Solomon Slow On BEST ANSWER

This is a comment, not an answer to your question.


Your two examples are not equivalent. The second one prevents more than one thread* from entering the synchronized block at the same time, but the first one can allow many threads to be in the block at the same time.

That's because, in the first one, synchronized(foo) locks whichever Integer object the foo variable happens to refer to at a given moment in time, but the foo++ statement reassigns foo so that it refers to a different object. Different threads could all be in the block at the same time, with each of them synchronized on a different object.

In the second example, fooLock is effectively immutable. It always refers to the same Object instance. Since every thread is trying to synchronize on the same object as every other thread, they will be forced to go through the block one at a time.


* Actually, both of your examples are complete programs that never create any new threads. In my comment, I'm imagining what would happen if the body of your main(...) function actually were in some other function that was called by many threads within some larger program.

0
Lefteris Laskaridis On

This is sometimes a design decision. Even though each java object has an instrinsic monitor that one could use for synchronisation, by using a private object's monitor instead we effectively encapsulate the class' synchronization policy. This is considered a safer practice because external users of the class cannot acquire the monitor and therefore interfere with the class synchronization policy.

Other times this may be done to improve performance (especially in concurrent data structures). For example ConcurrentHashMap uses a technique called lock striping, using several different monitors (instead of the class intrinsic monitor) to guard disjoint parts of the underlying data structure and thereby decrease contention during data access.

0
Nathan Hughes On

So the immediate issue that your example causes is that your code uses immutable objects, updates cause the variable to point to a new object, and the whole locking scheme falls apart.

Now forget about updates, say you have code that synchronizes on the Integer 10, and that value doesn't change so we don't have the issue above. What if some other code in the codebase also synchronizes on the integer 10? Integers below a certain value get cached, so there is only one of each of them in a given JVM. If these two code paths get exercised concurrently in the same JVM then you can have the possibility of two otherwise unrelated pieces of code where one blocks the other for no good reason except they happen to use the same object to synchronize on.

When you reuse something that has another usage there is the risk that, even if it works perfectly well now, some change in the future to that other usage will impact the locking scheme. Keeping the lock as a dedicated object means there is no danger of having other reasons to change the object.

In a large application it is hard to keep everyone in the loop about what to lock and what not to, people may reuse pre-existing code in unexpected ways. If you use synchronized without specifying a lock then you get the monitor on the object, and any code accessing that object can acquire the lock. Declaring a dedicated object within the object instance to use as a lock makes it harder for other parts of the codebase to interfere with an object's usage of the lock.