JAVA内存模型后续问题
A更新了本地副本x的值,不会主动刷新到主存中吗,必须等到和B通信时?
--A线程迟早会把更新过的X值刷新到主内存中,但具体会在什么时候刷新到主内存是不确定的。如果我们使用同步原语(synchronized,volatile和final),那么刷新的时机是确定的。比如,如果A线程释放了锁,它就会刷新本地内存A;接下来如果B线程获取同一个锁,B线程就会到主内存中去读取共享变量的值。此时,B线程就可以读取到A线程对共享变量所做的修改了(即“A线程释放锁--B线程获取锁”可以实现A,B线程之间的通信)。
另外,为什么是A和B通信,而不是B和A通信?
--这里的通信是指线程之间通过共享变量来沟通。由于共享变量在所有线程之间共享,如果A,B线程之间需要沟通,只需要A线程把写过的共享变量的值刷新到主内存,随后B线程到到主内存中去读取这些共享变量即可。这个过程可以让B线程看到A线程对共享变量锁做的修改,这种线程之间的沟通方式在本文中称之为通信。
在某个方法里面定义了一个数组,这个数组的对其他线程是不可见的,因为每个线程都有自己的独立内存空间,称之为Java方法栈,其生命周期和线程的生命周期一致,至于这个数组存放在哪里,可能是存放当前线程的PC寄存器里,PC寄存器可以利用java的堆内存也可以直接就存放在该PC寄存器的内存空间内。如果是存放在java 的堆内存中,对其他现场是可见的,但是没有线程去使用它,即就是本文所说没有通信,因此还是线程安全的。如果是直接 存放在寄存器内,效率很高。我想这就是线程栈封闭的一部分吧~
对象能否被多个线程共享和它存在哪里没有必然联系。这其实是要分清楚对象与对象的引用的差别。“实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享”这句话干脆理解成对象是存储在堆内存中的比较简单。而对象的引用可以理解成一个值,这个值就像是int、char这样的值一样,可以存储在Stack空间中,只不过对象的引用这个值是一个内存地址,而不是像int型的123或者char型的'V'。因此,如果是在方法体内调用的一个局部数组如:
void methodA(){
Object[] objects=new Object[]{new Object(),new Object(),new Object()};
}
显然,objects数组引用的3个对象是存在堆内存中的,而objects数组引用本身是存储在stack空间中的。但是,由于一个线程不能直接访问另外一个线程的stack空间,因此上述的数组事实上是无法在多个线程之间共享的。
多线程会导致占用重复的内存空间吗?
回答这个问题首先要弄清究竟什么是工作内存,什么是主内存(指RAM?指Java Heap内存?)。所以,我觉得还是不要去纠结什么是工作内存、什么是主内存。那只是JVM规范为了叙述方便弄出的术语(可能是考虑跨平台吧,我想)。其实,我们只需要弄清楚一点:处理器并不是直接访问RAM(事实上,它也称主内存),而是通过高速缓存(Cache)访问内存。也就是说,从高级语言代码的角度看的一个变量,我们可以理解为它是存在RAM中的。因此对变量的读写操作就是对RAM的读写操作。但是,处理器对我们所谓的读写内存操作实际上是对高速缓存的读写操作。以一个单处理器的系统为例,代码中的一个变量其实是同时占用一份RAM空间和一份高速缓存空间。那么,对于多核处理器的系统而言,这一个变量还是占用一份RAM空间,所不同的是它可能同时占用多份高速缓存空间。而缓存相关性(Cache Coherence)协议则是用来保证高速缓存之间以及高速缓存与RAM之间的数据的一致性。
内存占用主要有3个方面:1)一个线程就是一个对象,对象本身会占用内存空间;2)创建线程(即Thread实例)的时候,JVM会未每个这样的对象(对象本身占用内存)额外分配两个线程调用栈(callstack)所需的内存空间,一个用于跟踪Java方法调用,另一个用于跟踪本地(native)方法调用);3)线程运行过程中可能持有对其它对象的引用,只要这些线程没有终止,那么这些 被引用的对象就无法被垃圾回收。
性能下降,要具体问题具体分析,但是从一般的角度看,可以从上下文切换的角度去分析:一般地,线程数量越多,产生上下文切换就越多,其处理器时间开销也越大。这也就意味着,处理器用于执行应用代码的时间也相对减少。这呈现出来的效果就是应用性能下降。在Java8中可以使用JMC监控下Java应用的上下文切换数量(单位监控时间内的)。