Thread Safety and Lazy Init
Source: Effective Java, 3rd Edition — Chapter 11 by Joshua Bloch (Items 82–84)
This lesson closes Chapter 11: documenting thread safety, lazy initialization idioms, and why programs must not depend on the thread scheduler.
Item 82: Document thread safety
Every class should document how it behaves under concurrent use. synchronized in source is not documentation — Javadoc omits it, and thread safety is not all-or-nothing.
| Level | Meaning | Examples |
|---|---|---|
| Immutable | No sync needed | String, BigInteger |
| Unconditionally thread-safe | Internal sync covers all use | AtomicLong, ConcurrentHashMap |
| Conditionally thread-safe | Some sequences need external sync | Collections.synchronizedMap iterators |
| Not thread-safe | Client must sync each use | ArrayList, HashMap |
| Thread-hostile | Broken even with client sync | Unsynchronized static mutation |
Conditionally thread-safe types must say which sequences need external sync and which lock (often the map itself, not a view).
For unconditionally thread-safe classes, prefer a private final Object lock over synchronized methods — clients can’t hold your public lock (denial-of-service) and subclasses can’t accidentally share the monitor with you.
Document thread safety
1 / 2Class Javadoc lists synchronized on each method — thread-safe?
Pick an answer to see if you are right.
Item 83: Use lazy initialization judiciously
Default: normal initialization with final fields. Lazy init is an optimization (or breaks init cycles) — measure before using it.
Thread-safe patterns:
| Case | Idiom |
|---|---|
| Break init cycle | synchronized accessor |
| Lazy static field (performance) | Initialization-on-holder |
| Lazy instance field (performance) | Double-check with volatile |
// Static — holder class
private static class Holder {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return Holder.field; }
// Instance — double-check
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) {
synchronized (this) {
if (field == null)
field = result = computeFieldValue();
}
}
return result;
}
Lazy initialization
1 / 2Performance-motivated lazy static field in multithreaded code
Pick an answer to see if you are right.
Item 84: Don’t depend on the thread scheduler
Correctness and portability must not rely on which thread runs when. Keep runnable threads near the processor count; threads should block waiting for work, not busy-wait.
Don’t “fix” liveness with Thread.yield() or thread priorities — behavior varies by JVM and platform. Size thread pools appropriately (Item 80); use CountDownLatch, BlockingQueue, etc., instead of spinning.
Thread scheduler
1 / 2Program barely works; adding Thread.yield() fixes it on your laptop
Pick an answer to see if you are right.
Items 82–84 map
| Item | Move |
|---|---|
| 82 | Document thread-safety level; private lock when unconditional |
| 83 | final eager init; holder (static) or double-check volatile (instance) |
| 84 | Block, don’t spin; no yield/priority hacks |
Chapter 11 complete. Next: Serialization (Chapter 12, Items 85–87).