volatile 的作用主要是为了 synchronized 外面那个 if 的。如果不加 volatile 的话，会出现一个情况：有一个线程正在对 uniqueInstance 赋值，由于没有 volatile 关键字，uniqueInstance 可能返回一个初始化一半了的对象，然后这时恰好另外一个线程正好执行到 if (uniqueInstance == null)，得到结果为 false 然后直接返回, 但实际上这个 uniqueInstance 读到的值并没有初始化完成，导致返回了初始化了一半的对象，最后造成程序 crash 。
Intuitively, this algorithm seems like an efficient solution to the problem. However, this technique has many subtle problems and should usually be avoided. For example, consider the following sequence of events:
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.
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.