What they're testing
Whether you separate visibility from atomicity — two ideas people routinely merge.
The short answer~30 seconds
volatile guarantees every thread reads the latest value and forbids reordering around it, but does NOT make operations atomic: count++ on a volatile is still read-increment-write and still loses updates. synchronized gives both mutual exclusion and visibility. volatile suffices when you only OVERWRITE rather than read-then-compute — the canonical case being a boolean running flag that stops a loop.
The long answer
Visibility sounds abstract until you hit it: one thread sets running = false, the other loops on while (running) and never stops. Not because it hasn't "seen" it in a temporal sense, but because the JIT may hoist the read out of the loop — it sees nothing in the loop modifying the variable, so it reads once. volatile forbids that. It's also why this bug only appears after a few seconds of running, once the JIT has compiled the loop.
For counters, the modern answer isn't synchronized but AtomicInteger or LongAdder. AtomicInteger uses CAS, so it's lock-free and clearly faster under moderate contention; LongAdder spreads the count over cells and sums on read, winning decisively under high contention when writes outnumber reads. Picking the right one shows you thought about contention level, not just correctness.
One level deeper, both are defined by the happens-before relation in the Java Memory Model: a volatile write happens-before every subsequent volatile read of that variable, and an unlock happens-before the next lock on the same monitor. Speaking in those terms rather than "flushing the cache to RAM" is the difference between a correct model and an outdated metaphor.
// volatile ĐỦ: chỉ ghi đè, không đọc-rồi-tính
private volatile boolean running = true;
public void stop() { running = false; }
public void run() { while (running) { /* … */ } }
// volatile KHÔNG ĐỦ: đọc-tăng-ghi là ba thao tác
private volatile int count;
public void inc() { count++; } // vẫn mất cập nhật
// Đúng, và không khoá
private final AtomicInteger count = new AtomicInteger();
public void inc() { count.incrementAndGet(); }What they'll ask next
?Does double-checked locking need volatile?
Yes, and omitting it is the classic bug: without volatile, another thread can observe a non-null reference before the constructor has finished, because the allocation and the assignment may be reordered. In practice the holder idiom or an enum gives a singleton with none of these traps.
?What does synchronized lock on?
An instance method locks on this, a static method on the Class object. Which is why locking on this in a public class is risky: outside code can lock the same object and create a deadlock you don't control. A private lock object is safer.
These lose points
- Saying
volatilemakes operations atomic. It's the number-one misconception about the keyword. - Explaining it as "writes to RAM instead of cache". That model is wrong for modern CPUs; the JMM speaks in happens-before.