Which is the difference between AtomicReference and Synchronized?

Tinwor picture Tinwor · Mar 3, 2015 · Viewed 8k times · Source

Is there any difference between AtomicReference and Synchronized?
E.G.

public class Internet {
    AtomicReference<String> address;
    public String getAddress(){
        return address.toString();
    }
    public void setAddress(String address) {
        this.address.set(address);
    }

}

And I pass the class to some threads that try to use the class at the same time, is it the same thing if I use this:

public class Internet {
    String address;
    public String getAddress(){
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

And then in the thread use synchronized before access to the class?

Answer

Nathan Hughes picture Nathan Hughes · Mar 3, 2015

You didn't initialize the reference in the first example, it should probably be:

public class Internet {
    AtomicReference<String> address = new AtomicReference<String>();
    public String getAddress(){
        String s = address.get();
        return s == null ? null : s.toString();
    }
    public void setAddress(String address) {
        this.address.set(address);
    }
}

Where the access restriction is located is important. If you put the control within the object being accessed then it can have sole control of its invariants, which is much less fragile than relying on the threads to all synchronize properly, where one badly behaved accessing thread can corrupt the thing being accessed. So the first example is much better on that account.

If you change the second example so that the object has control over its own locking (so it is not relying on threads accessing it to do so safely), like this:

public class Internet {
    private final Object lock = new Object();
    private String s;
    public String getAddress() {
       synchronized(lock) {
           return s;
       }
    }
    public void setAddress(String s) {
        synchronized(lock) {
            this.s = s;
        }
    }
}

then it's a closer comparison, one relies on locking and the other on atomic references. The one using AtomicReference tries to avoid locking using machine-level atomic processing instructions. Which is faster may depend on your hardware and jvm and the processing load, typically the atomic approach should be faster. The synchronized approach is a more general purpose mechanism; with the synchronized block you can group together multiple assignments much more easily, where with atomic references it's much more involved.

As James says in his answer, with synchronization your threads are waiting for a lock; there is no timeout, and deadlock is possible. With the atomic reference the thread makes its change with no waiting on a shared lock.

The simplest and best-performing way to implement this would be to organize your code so that you can make the object immutable, so you would avoid all locking, busy-waiting, and cache updating:

public final class Internet {
    private final String s;
    public Internet(String s) {
        this.s = s;
    }
    public String getAddress() {return s;}
}

In descending order of preference:

  • Prefer immutability whenever possible.
  • For code that can't be immutable, try to confine mutation to a thread.
  • If only one thing has to change across threads, use the atomic approach.
  • If multiple changes across threads need to occur together undisturbed by other threads, use locking.