Java
内存模型FAQ
(七
)同步会干些什么呢
同步有几个方面的作用。最广为人知的就是互斥 ——一次只有一个线程能够获得一个监视器,因此,在一个监视器上面同步意味着一旦一个线程进入到监视器保护的同步块中,其他的线程都不能进入到同一个监视器保护的块中间,除非第一个线程退出了同步块。
但是同步的含义比互斥更广。同步保证了一个线程在同步块之前或者在同步块中的一个内存写入操作以可预知的方式对其他有相同监视器的线程可见。当我们退出了同步块,我们就释放了这个监视器,这个监视器有刷新缓冲区到主内存的效果,因此该线程的写入操作能够为其他线程所见。在我们进入一个同步块之前,我们需要获取监视器,监视器有使本地处理器缓存失效的功能,因此变量会从主存重新加载,于是其它线程对共享变量的修改对当前线程来说就变得可见了。
依据缓存来讨论同步,可能听起来这些观点仅仅会影响到多处理器的系统。但是,重排序效果能够在单一处理器上面很容易见到。对编译器来说,在获取之前或者释放之后移动你的代码是不可能的。当我们谈到在缓冲区上面进行的获取和释放操作,我们使用了简述的方式来描述大量可能的影响。
新的内存模型语义在内存操作(读取字段,写入字段,锁,解锁)以及其他线程的操作(start
和 join
)中创建了一个部分排序,在这些操作中,一些操作被称为happen before
其他操作。当一个操作在另外一个操作之前发生,第一个操作保证能够排到前面并且对第二个操作可见。这些排序的规则如下:
线程中的每个操作happens before
该线程中在程序顺序上后续的每个操作。
解锁一个监视器的操作happens before
随后对相同监视器进行锁的操作。
对volatile
字段的写操作happens before
后续对相同volatile
字段的读取操作。
线程上调用start()
方法happens before
这个线程启动后的任何操作。
一个线程中所有的操作都happens before
从这个线程join()
方法成功返回的任何其他线程。(注意思是其他线程等待一个线程的jion()
方法完成,那么,这个线程中的所有操作happens before
其他线程中的所有操作)
这意味着:任何内存操作,这个内存操作在退出一个同步块前对一个线程是可见的,对任何线程在它进入一个被相同的监视器保护的同步块后都是可见的,因为所有内存操作happens before
释放监视器以及释放监视器happens before
获取监视器。
其他如下模式的实现被一些人用来强迫实现一个内存屏障的,不会生效:
synchronized (new Object()) {}
这段代码其实不会执行任何操作,你的编译器会把它完全移除掉,因为编译器知道没有其他的线程会使用相同的监视器进行同步。要看到其他线程的结果,你必须为一个线程建立happens before
关系。
重点注意:对两个线程来说,为了正确建立happens before
关系而在相同监视器上面进行同步是非常重要的。以下观点是错误的:当线程A
在对象X
上面同步的时候,所有东西对线程A
可见,线程B
在对象Y
上面进行同步的时候,所有东西对线程B
也是可见的。释放监视器和获取监视器必须匹配(也就是说要在相同的监视器上面完成这两个操作),否则,代码就会存在“数据竞争”。