Senior (5+ years)Java

What is the difference between synchronized and volatile in Java, and how do you avoid deadlocks?

Quick answer

synchronized gives mutual exclusion and memory visibility for a block of code, while volatile only guarantees that reads and writes of one variable are visible to all threads and does not make compound operations such as count++ atomic.

Without synchronisation a thread may keep a cached copy of a variable and never see another thread's update. volatile forces reads and writes to go through main memory (and establishes a happens-before relationship), so it is suitable for simple flags such as volatile boolean running. It does not protect count++, which is really read, add, write, and can interleave with another thread.

For compound actions use synchronized, a ReentrantLock, or atomic classes like AtomicInteger and LongAdder, which use compare-and-swap. A deadlock needs four conditions: mutual exclusion, hold and wait, no preemption and circular wait. You prevent it by always acquiring locks in a consistent global order, holding locks for as short a time as possible, using tryLock with a timeout, or avoiding shared mutable state altogether with immutable objects, message passing and higher-level utilities from java.util.concurrent. A thread dump (jstack) reports detected deadlocks.

private volatile boolean running = true;     // visibility only
private final AtomicInteger counter = new AtomicInteger();

void work() {
    while (running) {
        counter.incrementAndGet();           // atomic, no lock
    }
}

Key points

  • volatile: visibility, not atomicity
  • synchronized / locks: mutual exclusion and visibility
  • Prevent deadlocks with consistent lock ordering and timeouts