user1364534
user1364534

Reputation: 105

Synchronization with copied references for compound statements in Java

Let's say we have an object whose methods/fields are synchronized on "this". This question is actually about "this" as I think I have a hard time what "this" reference means.

So our object is:

class A {
    private Field a;
    private Field b;

    public synchronized void doSomething() {
       //something with a
    }

    public synchronized void somethingElse() {
       //do something as with b
    }

}

Then we have another object or method that takes A objects and do work on a and b via doSomething and somethingElse methods. So I need to keep the state consistent while I process A objects, hence I synchronize. Let's say those A objects are values of a Map. Then I am iterating over the values and do the things that I do. So the question is, is it thread-safe to do it in the following way:

 for(A aObject : map.values()) {
     synchronized(aObject) {
          aObject.doSomething(); 
          aObject.somethingElse();
     }
 }

If the "this" reference is the same reference as aObject, I think I shouldn't be in trouble. But what if I do it like this:

for(A aObject : map.values()) {
      A anotherReference = aObject;

      synchronized(anotherReference) {
         anotherReference.doSomething(); 
         anotherReference.somethingElse();
      }

}

Is it still thread-safe? I mean can I synchronize on a local copy of a lock reference?

Note: this is an oversimplification of something I need to do in my code.

Upvotes: 1

Views: 214

Answers (3)

jtahlborn
jtahlborn

Reputation: 53694

you seems to be confused about what references are, so i would go read up on them. when you use a synchronized block, you are not synchronizing on the reference itself, but on the object instance to which the reference refers.

for example:

Object a = new Object();
Object b = a;

synchronized(a) { ... }
synchronized(b) { ... }

these two synchronized blocks are synchronizing on the same Object instance because a and b refer to the same Object instance.

Following from that, a synchronized method is the same as synchronizing on the this reference.

for example:

public class A { public synchronized void doStomething() { ... } public void doSomethingElse() { synchronized(this) { ... } } }

both of these methods are synchronizing on the same Object instance (the current instance), using the self reference known as this. you can rewrite either example as the other example and they are equivalent.

So, returning to your original example, i hope you will understand that when you synchronized on an Object instance externally through a reference (as my first example), it is doing the same thing as an Object synchronizing internally on itself.

To wrap, your last example is a common idiom when working on synchronized collections as it enables the caller to ensure that 2 operations are performed atomically with respect to the Collection.

for example:

// this will result in a List where all methods are internally synchronized
List<Object> syncList = Collections.synchronizedList(new ArrayList<Object>());

// i can safely perform an atomic operation on the List using this pattern
synchronized(syncList) {
  if(syncList.isEmpty()) { // <- synchronized method call
    syncList.add(...); // <- synchronized method call
  }
}

Upvotes: 1

Ian Roberts
Ian Roberts

Reputation: 122364

The synchronisation monitor belongs to the object that is referenced, not the reference, so your two for loops are equivalent, they both synchronise on the same object.

Now a synchronized method

public synchronized void foo() {
  // do stuff
}

is exactly equivalent to

public void foo() {
  synchronized(this) {
    // do stuff
  }
}

so in the loop

for(A aObject : map.values()) {
    synchronized(aObject) {
         aObject.doSomething(); 
         aObject.somethingElse();
    }
}

the synchronized block is locking the same monitor as the doSomething() and doSomethingElse() methods use themselves. What you gain from the synchronized block is that no other thread can sneak in and call either of those methods on the same A instance in between these two calls.

Upvotes: 3

Luigi Massa Gallerano
Luigi Massa Gallerano

Reputation: 2357

In Java we have two basic synchronization idioms: synchronized methods and synchronized statements.

When you use the first idiom (synchronized methods) as in the following code:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

You have two main effects:

1) it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

2) when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Another way to create synchronized code is with synchronized statements. Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

In your code you're using both of these idioms. Then, your first for loop doesn't need the synchronized(aObject) because your class methods are already synchronized methods.

source: http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

But let's say your class methods weren't synchronized. Your second code example:

for(A aObject : map.values()) {
      A anotherReference = aObject;

      synchronized(anotherReference) {
         anotherReference.doSomething(); 
         anotherReference.somethingElse();
      }

}

Still works, because in Java every object has an intrinsic lock associated with it. When you call synchronized(Object o) you're acquiring the lock associated with the Object: anotherReference, that in your case is the aObject.

Let's think about two Thread: T1 and T2. If T1 call this for loop before T2, it acquires the intrinsic lock associated with the aObject and T2 won't able to do the same till T1 will end both methods: doSomenthing() and somethinElse().

Upvotes: 0

Related Questions