Đoạn kinh điển sau hỏng nếu instance không volatile:
private static Singleton instance; // THIẾU volatile
static Singleton get() {
if (instance == null) { // đọc lần 1, không lock
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}instance = new Singleton() không phải một thao tác nguyên tử. Nó gồm ba bước: cấp phát bộ nhớ, chạy constructor, gán địa chỉ vào instance. JMM cho phép compiler và CPU đảo thứ tự bước 2 và 3.
Nếu thứ tự thành cấp phát → gán → chạy constructor, một thread khác đọc instance ở dòng ngoài (không giữ lock, nên không có happens-before nào với thread khởi tạo) sẽ thấy tham chiếu khác null nhưng trỏ tới object chưa constructor xong — các field còn giá trị mặc định. Bug này không tái hiện được ổn định và phụ thuộc JIT lẫn kiến trúc CPU.
Sửa: khai báo private static volatile Singleton instance. Từ Java 5, ghi vào biến volatile happens-before mọi lần đọc sau đó, nên mọi thao tác trong constructor đều hiện đầy đủ trước khi thread khác thấy tham chiếu.
Đơn giản hơn: dùng holder idiom — private static class Holder { static final Singleton INSTANCE = new Singleton(); }. JVM bảo đảm khởi tạo class chạy đúng một lần và an toàn thread, không cần lock thủ công.