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