單利模式為何要用volatile
防止指令重排:對volatile的寫操作先於對volatile的讀操作。
synchronized雖然保證了原子性,但卻沒有保證指令重排序的正確性,會出現A線程執行初始化,但可能因為構造函數裏面的操作太多了,所以A線程的uniqueInstance實例還沒有造出來,但已經被賦值了。而B線程這時過來了,錯以為uniqueInstance已經被實例化出來,一用才發現uniqueInstance尚未被初始化.
36
down vote
The volatile prevents memory writes from being re-ordered, making it impossible for other threads to read uninitialized fields of your singleton through the singleton‘s pointer.
Consider this situation: thread A discovers that uniqueInstance == null, locks, confirms that it‘s still null, and calls singleton‘s constructor. The constructor makes a write into member XYZ inside Singleton, and returns. Thread A now writes the reference to the newly created singleton into uniqueInstance, and gets ready to release its lock.
Just as thread A gets ready to release its lock, thread B comes along, and discovers that uniqueInstance is not null. Thread B accesses uniqueInstance.XYZ thinking that it has been initialized, but because the CPU has reordered writes, the data that thread A has written into XYZ has not been made visible to thread B. Therefore, thread B sees an incorrect value inside XYZ, which is wrong.
When you mark uniqueInstance volatile, a memory barrier is inserted. All writes initiated prior to that of uniqueInstance will be completed before the uniqueInstance is modified, preventing the reordering situation described above.
解釋:維基百科
- Thread A notices that the value is not initialized, so it obtains the lock and begins to initialize the value.
Due to the semantics of some programming languages, the code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization. For example, in Java if a call to a constructor has been inlined then the shared variable may immediately be updated once the storage has been allocated but before the inlined constructor initializes the object.[6]
Thread B notices that the shared variable has been initialized (or so it appears), and returns its value. Because thread B believes the value is already initialized, it does not acquire the lock. If B uses the object before all of the initialization done by A is seen by B (either because A has not finished initializing it or because some of the initialized values in the object have not yet percolated to the memory B uses (cache coherence)), the program will likely crash.
單利模式為何要用volatile