网上搜了许久,所有关于可见性问题都是用的原生类型举例。
我的疑问是,如果变量是Java.Collection,一个线程调用了add方法。不加同步机制,其他的线程也看不到这个改动吗?
我觉得对于分配在堆上的对象,JVM不可能会为每一个线程都deep copy一份。所以,每个线程引用到的都是堆上的那个对象。
有些人说,CPU会在自己的缓存里缓存add方法调用的CPU指令,而不是立即刷出到主存里。如果是这样的话,对象的方法调用确实会存在不可见问题,但我在自己机器上测试,并没有出现不可见问题。按理说,多核心CPU的设计不应该这么弱智,这种情况只可能会出现在多CPU情况下。
疑惑中
[quote]
如果CPU缓存里放了在heap里的对象,那JSP里的:Memory that can be shared between threads is called shared memory or heap memory.这句话就有点问题了,内存数据不再是共享的状态。
看一下这里:
http://www.artima.com/insidejvm/ed2/jvm2.html
有一句话:
A thread's Java stack stores the state of Java (not native) method invocations for the thread. The state of a Java method invocation includes its local variables, the parameters with which it was invoked, its return value (if any), and intermediate calculations.
似乎线程并不能copy在堆上的对象。我说效率低,是因为如果堆上的对象实际就是程序的领域对象了。这个对象集合可能非常大。比如一个graph数据结构,起内部的点边是相互关联的,线程遍历这个graph,修改每一个节点每一条边,如果依次copy对象到CPU缓存,效率是不可接受的。copy一个也许是OK的
[/quote]
JVM是由线程触发(启动)的,栈里面的内容是不会出现多线程安全的问题,栈里面的内容相当于ThreadLocal,OS线程利用CPU,关键是CPU会把内存中的数据Copy到Cache或寄存器中,如果是多核CPU中,多个线程分布在不同的核上,Cache或寄存器上就相互独立,最后提交到内存中,可能数据出现了不一致。
一般的对象是Cache足够了,大对象是有小对象组成的(或称为数据),二级缓存现在都在2MB以上,已经足够,至少可以缓存部分数据,其他部分再想内存获取,一般情况CPU会利用Cache或寄存器,而不会直接走内存,除非特殊指令控制以外,你去看看自己主板的前端总线的传输效率,常识,CPU的缓存比内存快很多很多!
原则上应该是都可见,但我个人理解是可见不能就能访问(读和写)。
:D
之所有你可能看到变化,是因为Java会保持最终一致性,线程修改的内容(在Cache或寄存器中)最终会提交(同步)内存中。
[quote]原则上应该是都可见,但我个人理解是可见不能就能访问(读和写)。[/quote]
可见是说能不能看到其他线程的修改后内容,读写和可见无关,无论可见与否,读写都是可以的。 :arrow:
[quote]
这是Java Language Specification里的:
17.4.1 Shared Variables
Memory that can be shared between threads is called shared memory or heap memory.
所以,关于这个问题,如果不可见,也只可能是CPU缓存缓存了操作指令。JVM copy 对象是不可能的,那样的话效率太低了。
[/quote]
效率低?不解啊,CPU会从内存中Copy数据的snapshot到cache或集群器中啊,JVM的内存模型是基于OS Thread模型和硬件架构汇编指令的。
[quote]
但你说的java最终会保持一致性,这个有点模糊。如果是thread stack里的数据,比如原生类型。一定会在stack出栈的时候才会刷出到主存。但这是JVM所控制的。我举的例子,似乎超出了JVM的控制范围。刷出CPU缓存数据到主存应该是由CPU自身的控制器所决定的。。。
[/quote]
线程Stack保存时临时变量,只是暂时Copy数据,Stack和Heap数据是两码事,并且Heap数据不一定会及时提交到主存中区。JVM会有最终一致性的实现,会让数据在某个时刻提交中主存,你你本机的测试是在并发压力不大情况下,如果压力大时,不会马上提交到内存,CPU到内存之间的总线传输开销是存在的。线程会利用CPU寄存器或N级缓存中的内容,而不是直接交互内存。