class SingleInstance{ private static single = null; private SingeInstance(){} public SingleInstance getSingleInstance(){ if(single == null)//0 synchroznied(SingleInstance.class){ if(single == null){ single = new SingleInstance();//1 } } return single; } }
如上代码的问题是在1处不能保证有序性,即这句代码其实分为两个大的步骤
这个步骤前后是不确定的。当线程一运行到1处的时候可能会先对象赋值给single了但是此时的single还没有初始化完成。线程2运行的0处的时候会发现这个条件是不符合的于是就返回了single。这时候的single虽然是一个非空的引用,但却不是一个正确的对象。 这个就是双重校验可能出现的问题。
可能你听说过JDK1.4以后用volatile修饰变量single可以解决这个问题,可你知道为什么能解决吗?
volatile的语义是能保持 有序性 和 可见性 ,但是不能保证 原子性
什么是可见性?
以
count = 0; couont++;
为例这个行代码的执行过程如下:
在多线程的情况下
看到问题了吧,加了两遍还是1,出事了啊兄弟!
volatile内在其中起什么作用呢?
当用volatile修饰count后这样线程执行操作的时候也就是上述的****2 步骤 他不会在副本中取值,而是去主存中取值。
即便是这样也不能解决计数问题,为什么呢?
volatile的可见性语义是保证线程进行操作也就是上述的步骤2是从主存中取最新的值而不是在自己副本中取值。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
线程A中 context = initContext();//1 flag = true;//2 线程B中 while(!flag){ sleep(100); } dosomething(context);
在单线程中代码是没有问题的,但是如代码清单二中,线程A的代码可能会发生重排序也就是运行代码2再运行代码1这就有问题了。
如果用volatile修饰就会禁止他进行重排序。
原子性简单来说就是不可分割,如果是原子操作,那它必定是要么被执行完毕,要么完全没执行两种情况之一,不可能出现执行了一部分这种情况。
volatile字段能保证可见性、禁止重排序,但并不能提供原子性。原因在于在多线程的条件下,不能保证执行顺序,中间会有线程切换的情况出现。
回到代码清单1还记得当初的问题吗?代码清单1中这个单例有什么问题我们已经说过了。怎么解决呢? 其实有了volatile这个关键字就好解决了,在single这个变量上添加volatile就可以完美解决了。原因是volatile具有禁止重排序的功能。所以会先进行初始化对象再赋值给变量,0处检测到的single不为空的时候就能正确返回single而不再是一个不完整的single了。