JAVA多线程的内存可见性

3/8/2017来源:ASP.NET技巧人气:1259

1.java内存模型(JMM)

Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤: 1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。 2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。 

如果A对共享变量进行修改了,在B线程中可以及时的看到修改之后的值,那么我们说对于线程B来说,共享变量对于其是可见的。否则就是不可见的,这就是我们要讨论的可见性。

导致共享变量在线程之间不可见的原因:

1、线程的交叉执行   2、重排序结合线程交叉执行   3、共享变量更新后的值没有在工作内存与主内存间及时更新

很显然,从上面的结果当中,3.我们是可以理解的。但是为什么线程的交叉执行会导致不可见呢?

举个例子:

假设存在线程A和线程B,共享变量X,A与B的执行的代码也是相同的,都是先读取X,然后将X的值进行修改,再存回去。在这个过程当中,由于线程之间是会竞争CPU资源的,所以在执行到什么语句会丧失CPU的资源是不确定的。假如线程A先读取了变量X = 1, 而后CPU资源就给B线程抢走了,B对X做了修改,令X++,再存了回去,即X = 2。最后线程A由于JMM的缘故,读取的X是在自己的工作内存当中,所以仍然是X = 1,再X++,最后X还是等于2。也就是说存在线程执行了两次X++,但最终X的结果只增加了一次的情况。在这种情况下,内存的可见性就不存在了。

保证可见性的条件如下:

1.共享变量的值的变化是原子语句。

2.共享变量在工作内存和主内存之间需要及时的更新。

1.通过synchronized关键字来实现可见性

我们都知道,可以通过synchronized关键字来实现同步性。在JMM中是这样规定的:

1、线程解锁前,必须把共享变量的最新值刷新到主内存中 2、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值

所以,在synchronized关键字的代码块中,互斥代码的实现过程是这样的:

1、获得互斥锁   2、清空工作内存   3、从主内存拷贝变量的最新副本到工作内存   4、执行代码   5、将更改后的共享变量的值刷新到主内存   6、释放互斥锁

由于synchronized关键字保证了代码块的原子性,又保证了共享变量的及时更新,所以显然,synchronized关键字是可以实现可见性的。

2.通过volatile关键字来实现可见性

volatile关键字同样可以实现可见性。

volatile关键字的作用如下:

1.对volatile变量执行写操作时,会在写操作后加入一条store屏障指令

2.对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

也就是说,volatile关键字实现了共享变量的及时更新。

但是值得注意的是,volatile关键字没办法保证语句的原子性,所以如果要实现可见性,需要一个前提条件:共享变量的操作必须是原子性的。

参考:

http://blog.csdn.net/suifeng3051/article/details/52611310    JAVA内存模型

http://www.cnblogs.com/zhilu-doc/p/5778180.html          概括的非常好,囊括了重点