本文来源于文档,但位置丢失,所以发布时只能选择原创无法选择为转载和翻译,这里附上官方地址: JSR内存模型和线程规范
一、 关于同步和管程
有多种机制可以用于线程间的通信。其中最基础的是同步,而同步是用管程来实现的。每个对象都关联着一个管程,线程可以通过它来执行锁定(lock) 或解锁(unlock)操作。每次仅有一个线程可以持有管程上的锁。其它试图锁定该管程的线程会一直阻塞,直到能从该管程上获得锁为止。线程 t 可以锁定一个管程多次,每次unlock操作都会将一次lock操作撤销。
synchronized 语句需要一个对象的引用;随后会尝试再改对象的管程上执行lock动作,如果lock动作未能成功完成,将一直等待。当lock 动作执行成功,就会运行 synchronized 语句块中的代码。一旦语句块中的代码执行结束,不管是正常还是异常结束,都会在lock动作的哪个管程上自动执行一个unlock 动作(这个lock不是指 Doug Lea 写的 Lock 接口,别弄混了)。
synchronized 方法在调用时会自动执行一个lock方法。在lock动作成功完成之前,都不会执行方法体。如果是实例方法,锁的是调用该方法的实例(即,方法体执行期间的this)相关的管程。如果是静态方法,锁的是定义该方法的类所对应的 Class 对象。一旦方法体执行结束,不管是正常还是异常结束,都会在之前执行lock 动作的哪个管程上自动执行一个 unlock 动作。
语义既不阻止也不要求对死锁条件进行检测。程序中若线程会(直接或间接地)锁定多个对象,应当采取一些手段来避免死锁,若有必要,创建一些更高级别的不会死锁的锁原语。
二、指令重排导致出人意料的结果
如上图,程序中用到了局部变量r1和r2,以及共享变量 A和B,可能出现 r2 = 2,r1 =1这样的结果。直觉上,要么指令1执行要么指令3先执行。如果指令1先执行,它不应该看到指令4中写入的值;如果指令3先执行,它不应该能看到指令2写的值。
如果某次执行表现除了这样的行为,那么我们可能得出这样的结论,指令4要在指令1之前执行,指令1要在指令2之前执行,指令2要在指令3之前执行,指令3要在指令4之前执行。如此,从表面看来,有悖常理(出现环了)。
然而从单个线程的角度来看,只要重排序不会影响到该线程的执行结果,编译器就可以对该线程中的指令进行重排序。如果指令1与指令2重排序,那就很容易看出未什么出现 r2 = 2和r1 =1 这样的结果了。
需要注意的是,这段代码没有被从分同步:
1.一个线程里有个写操作
2.另一个线程读取了这个写入的变量值
3.且读写操作没有被同步排序
当上述情况发生时,称之为:存在数据争用(data race).当代码中存在数据争用时,常有可能出现有违直觉的结果。
有几种机制都可以产生上图的重排序。JIT 编译器和处理器可以对代码进行重新整理。此外,运行JVM的机器分级存储系统可以使代码看起来像是被重排序过。为简单起见,任何能够对代码进行重排序的东西,我们称之为编译器。源代码到字节码的转换过程中可以重排序和改变程序,但必须是本规范(JSR内存模型和线程规范)允许的那些方式。