Why do we need to specify the lock for synchronized statements?

1.5k views Asked by At

Given that there's only one lock for each instance of a class, then why doesn't Java just allow us to do this:

void method() {
    synchronized {
        // do something
    }

    // do other things
}

instead of this:

void method() {
    synchronized (lock) {
        // do something
    }

    // do other things
}

What's the purpose of specifying a lock? Does it make a difference if I choose one object as a lock over the other? Or could I just choose any random object?

EDIT:

It turned out that my comprehension of synchronized methods is wrong at the fundamental level.

I thought different synchronized methods or blocks are entirely independent of each other regardless of locks. Rather, all synchronized methods or blocks with the same lock can be accessed only by one thread, even if such synchronized methods/blocks are from different classes (the documentation should have emphasized this more: ALL synced methods/blocks, regardless of location, all that matters is the lock).

5

There are 5 answers

1
janos On BEST ANSWER

Given that there's only one lock for each instance of a class, then why doesn't Java just allow us to do this:

void method() {
    synchronized {
        // do something
    }

    // do other things
}

Although an intrinsic lock is provided with each instance, that's not necessarily the "obvious" lock to use.

You're perhaps right that they could have provided synchronized { ... } as a shorthand for synchronized (this) { ... }. I don't know why they didn't, but I never missed it. But concurrent programming is tricky, so making the lock object an explicit required parameter may make things clearer to readers, which is a good thing, as @ajb pointed out in a comment. In any case, I don't think syntax is your main question, so let's move on.

What's the purpose of specifying a lock?

Uhm, the lock is perhaps the single most important thing in the synchronization mechanism. The key point in synchronization is that only one thread can hold the same lock. Two threads holding different locks are not synchronized. So knowing what is the lock guarding the synchronization is crucial.

Does it make a difference if I choose one object as a lock over the other?

I hope the previous section makes it clear that yes, you have to choose the object carefully. It has to be an object visible by all threads involved, it has to be not null, and it has to be something that won't get reassigned during the period of synchronization.

Or could I just choose any random object?

Certainly not. See the previous section.

To understand concurrency in Java, I recommend the book Java Concurrency in Practice by one of the authors of the API, or Oracle's tutorials on the subject.

0
Chinmay On

In synchronized (lock).., lock can be an object level lock or it can be class level lock.

  • Example1 Class Level Lock:

    private static Object lock=new Object();
    synchronized (lock){
    //do Something
    }
    
  • Example2 Object Level Lock:

    private Object lock=new Object();
    synchronized (lock){
    //do Something
    }
    
0
David Ehrmann On

It's so you can lock on something completely different than this.

Remember how Vector is "thread-safe?" It's not quite that simple; each call is, but code like this isn't because it could have been updated between getting the size of the vector and getting the element:

for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i));

Since Vector, along with Collections.synchronized*, is synchronized with the older synchronized keyword, you can make that above code thread-safe by enclosing it all within a lock:

synchronized (vector) {
    for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i));
}

This could be in a method that isn't thread-safe, isn't synchronized, or uses ReentrantLock; locking the vector is separate from locking this.

0
G.Bob On

if you have multiple objects b1/b2 needs to update concurrency

class A {
    private B b1, b2;
}

if you have only one lock say class A itself

synchronized (this) { ... }

then assume there are two threads are updating b1 and b2 in the same time, they will play one by one because synchronized (this)

but if you have two locks for b1 and b2

private Object lock1 = new Object, lock2 = new Object;

the two threads i've mentioned will play concurrently because synchronized (lock1) not affect synchronized (lock2).sometimes means better performance.

0
ajb On

It most certainly makes a difference what object you use as a lock. If you say

void method() {
    synchronized (x) {
        // do something
    }

    // do other things
}

Now, if one thread is executing the block and another tries to enter the block, if x is the same for both of them, then the second thread will have to wait. But if x is different, the second thread can execute the block at the same time. So, for example, if method is an instance method and you say

void method() {
    synchronized (this) {
        // do something
    }    
    // do other things
}

Now two threads running the method using the same object can't execute the block simultaneously, but two threads can still run the method on different objects without blocking each other. This is what you'd want when you want to prevent simultaneous access to the instance variables in that object, but you don't have anything else you need to protect. It's not a problem if two threads are accessing variables in two different objects.

But say the block of code is accessing a common resource, and you want to make sure all other threads are locked out of accessing that resource. For example, you're accessing a database, and the block does a series of updates and you want to make sure they're done atomically, i.e. no other code should access the database while you're in between two updates. Now synchronized (this) isn't good enough, because you could have the method running for two different objects but accessing the same database. In this case, you'd need a lock that is the same for all objects that might access the same database. Here, making the database object itself the lock would work. Now no two threads can use method to enter this block at the same time, if they're working with the same database, even if the objects are different.