关于synchronized具备原子性的问题
1 原子性的定义:
原子操作(atomic operation)是不需要synchronized,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
2 深入理解Java虚拟机对synchronized的描述:
java内存模型提供了lock和unlock操作来满足更大范围的需求,尽管虚拟机没有直接lock和unlock开放给用户使用,但却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,这两个字节码反映到Java代码中就是同步块synchronized关键字,因此synchronized块之间额操作也具有原子性。
3 我的问题,如果说程序中有一个特别耗时的synchronized块,在单cpu的环境下运行,因其原子性,岂不会阻塞其他程序的运行,这肯定是不允许的?
4 搜索资料
Double Check Lock Singleton:下面是一个dcl单例模式的代码段,举这个例子一方面解释synchronized块是否可中断性,一方面解释dcl singleton是否具有多线程安全性。
class Singleton {
private static Singleton instance = null;
public static Singleton instance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();//step1
}
}
return instance;
}
}
为了方便说明,通过分析对象实例化的汇编代码,来确定dcl singleton是否具有多线程安全,而其不安全正是因为在synchronized块不恰当的中断导致的,也就解释了上述的疑问。
设一行Java代码:
Objects[i].reference = new Object();
经过Symantec JIT编译器编译过以后,最终会变成如下汇编码在机器中执行:
[pre]
0206106A mov eax,0F97E78h
0206106F call 01F6B210 ;为Object申请内存空间
; 返回值放在eax中
02061074 mov dword ptr [ebp],eax ; EBP 中是objects[i].reference的地址
; 将返回的空间地址放入其中
; 此时Object尚未初始化
02061077 mov ecx,dword ptr [eax] ; dereference eax所指向的内容
; 获得新创建对象的起始地址
02061079 mov dword ptr [ecx],100h ; 下面4行是内联的构造函数
0206107F mov dword ptr [ecx+4],200h
02061086 mov dword ptr [ecx+8],400h
0206108D mov dword ptr [ecx+0Ch],0F84030h
[/pre]
可见,Object构造函数尚未调用,但是已经能够通过objects[i].reference获得Object对象实例的引用。
如果把DCL单例代码放到多线程环境下运行,某线程在执行到step1代码的时候JVM或者操作系统进行了一次线程切换,其他线程显然会发现instance 对象已经不为空,导致Lazy load的判断语句if(instance == null)不成立。线程认为对象已经建立成功,随之可能会使用对象的成员变量或者调用该对象实例的方法,最终导致不可预测的错误。
5 我的结论
synchronized块不同于真正意义的原子性操作,执行时是可以中断的,通过锁实现了线程访问共享数据的串行化。DCL单例模式在多线程的环境下是不安全的,若线程t1恰好在instance引用获得内存地址,但是对象尚未构造完成时被中断,线程t2发现instance引用已经!=null,直接调用instance的方法和属性,将导致意想不到的错误(对象锁仍然属于线程t1)。