Java内存的可见性

待兔
• 阅读 402

Java内存的可见性 可见性: 一个线程对共享变量的修改,能够及时被其它线程看到

共享变量: 如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量

Java内存模型(JMM): 描述了Java程序中各种线程共享变量的访问规则,以及在JVM中将线程共享变量存储到内存和从内存中读取出线程共享变量这样的底层细节

上面这些规则都是针对线程的共享变量的,JMM的细节会在以后的博客里面写。 本篇只需要知道

1 所有的变量都存储在主内存中 2 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本,主要是针对共享变量 下面使用一张图来表示主内存,工作内存(也叫做本地内存),线程,共享变量之间的关系

Java内存的可见性

由上图可知:

1 每个线程都有自己的工作线程

2 每个线程只能操作自己的工作内存,不能直接操作主内存

3 每个线程都有一个主内存中的变量a的一个副本,所以,变量a就叫做这三个线程的共享变量

在讲变量在内存中的可见性之前,先看下JMM(JAVA内存模型)中的两条规定

1 线程对共享变量的所有的操作都必须在自己的工作内存中进行,不能直接从主内存中读写

2 不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量的传递需要通过主内存来完成

这两条规定也可以从上面的图中可以看出来。

共享变量可见性实现的原理

问:线程1对共享变量的修改如何被线程2及时的看到? 主要经过以下2个步骤

把线程1中的工作线程中,更新过的共享变量a,刷新到主内存中 将主内存中最新的共享变量a的值,更新到线程2的工作内存中 经过了上面两个步骤后,线程1对共享变量的修改,及时的更新到主内存中

线程2将主内存中的最新的共享变量的值,刷新到自己的工作内存中

线程1 和 线程2 中,a的值就一样了,都是最新的,这时候就说共享变量a在线程1和线程2中是可见的。

由于上面的JMM的两条规定,线程都是在自己的工作线程中操作变量,线程不能直接和主内存进行交互,线程之间必须通过主内存进行交互

在多线程编程中,就会出现下面这两种情况:

线程1 修改了共享变量的值,但是没有及时的更新到主内存中去,线程2读取到的值就不是最新的。 线程2 修改并及时的更新了共享变量的值,但是线程2没有及时的读取到,也会导致线程读到到的值不是最新的。 由此产生了共享变量a在线程1和线程2中是不可见的,理想的情况下,我们想要实现的是共享变量对所有访问它的线程都是可见的。

不可见往往会导致很多严重的问题,导致数据的不一致性。多线程编程中,要保证线程间的可见性

如何实现共享变量的可见性?要实现共享变量的可见性,必须保证两点:

线程修改后的共享变量的值能够及时从工作内存中刷新到主内存中 其它线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中

JAVA在语言层面支持的可见性实现方式有哪些?

synchronized volatile

1 synchronized实现可见性。

synchronized 的两个作用:

1 原子性(同步)

2 可见性

很多同学对第一种synchronized同步比较了解,都知道。也经常用,其实synchronized还能实现内存的可见性的。

JMM关于synchronized的两条规定:

线程解锁前,必须把共享变量的最新值刷新到主内存中 线程加锁时,将清空工作内存中共享变量的值,然后从主内存中重新读取共享变量的值(注:加锁与解锁需要是同一把锁) 以上两条规定保证了 线程解锁前对共享变量的修改在下次加锁时对其它线程可见

synchronized线程执行互斥锁代码的过程如下:

1 在synchronized的入口处,获得互斥锁

2 获得互斥锁后,清空工作内存

3 从主内存中拷贝变量的最新值到工作内存

4 执行代码

5 执行完代码后,共享变量的值有可能发生变化,这时会将共享变量的值刷新到主内存中

6 释放锁

在演示代码前,先了解一个事件

问:程序的执行顺序一定是按照代码的书写的顺序执行的吗?

答:答案是 否是的。即不一定是按照代码的书写顺序执行的,主要是因为编译器或者处理器做了优化,即指令重排序

问:为什么要有重排序?重排序有什么好处?

答:编译器或者处理器为了提高程序的性能而做的优化,更加符合处理器执行效率。

问:指令重排序不会打乱了程序的逻辑吗?

答:不会,因为JMM有一条规定,as-if-serial原则,即保证在单线程里面,指令重排序前和重排序后,执行的结果是一致的。

如下面的例子,a,b的赋值顺序不同,但不会影响sum的值,注:是在单线程里面。

重排序前: 重排序后:

a = 1 b = 1

b = 1 a = 1

sum = a + b sum = a + b

指令重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或者处理器为了提高程序性能而做的优化

编译器优化的重排序(编译器优化) 指令级并行重排序(处理器优化) 内存系统的重排序(处理器优化) as-if-serial原则:单线程里,无论怎么重排序,程序的执行结果是一致的

如上面的例子,无论前面两句怎么排序,最后一句sum = a + b 是不能排序的。这样就保证了程序的结果一致性

结论:

1 重排序不会给单线程带来内存可见性问题

2 多线程程序交错执行时,重排序可能会造成内存可见性问题

今天就先写到这里,下一篇会有代码来讲演示上面的理论

点赞
收藏
评论区
推荐文章
灯灯灯灯 灯灯灯灯
3年前
一次性带你了解清楚Java内存模型!
Java内存模型咳咳咳,能看完的都是人上人。。。。Java虚拟机内部使用JMM(Java内存模型)将内存划分为两个逻辑单元,线程栈(或者叫本地内存)和堆。每一个线程都有属于自己的线程栈,在线程栈中会保存局部变量(也叫做本地变量)、方法中定义的参数和异常处理器的参数(catch中的参数);这些参数和变量都属于线程局部操作,会被隔离,所以不受内存模
Wesley13 Wesley13
3年前
java之jvm
1.JVM内存模型_线程独占:栈,本地方法栈,程序计数器线程共享:堆,方法区_回答以上问题是需回答两个要点:1\.各部分功能2\.是否是线程共享2.JMM与内存可见性JMM是定义程序中变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作.由于指令重排序,读写的顺序会被打乱,因此JMM需要
Wesley13 Wesley13
3年前
Java并发(六):volatile的实现原理
synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronizedvolatile是一个变量修饰符,只能用来修饰变量。volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。volatile读:当读一个volatile变量时,JMM会把该线程对应的
Wesley13 Wesley13
3年前
Java 内存模型
什么是Java内存模型?JMM(JavaMemoryModel,Java内存模型),它定义了多线程访问Java内存的规范。简单的说有以下几部分内容:Java内存模型将内存分为主内存和工作内存定义了几个原子操作,用于操作主内存和工作内存中的变量定义了volatile变量的使用规则happensbefor
Wesley13 Wesley13
3年前
Java多线程(二)
\恢复内容开始一,volatile关键字当多个线程操作共享数据时,可以保证内存中的数据可见性相较于synchronized关键字:1,不具备“互斥性”2,不能保证变量的原子性二,原子变量volatile保证内存可见性CAS(CompareAndSwap)算法保证数据的原子性内存值V预估值A更新值
Wesley13 Wesley13
3年前
Java内存模型
注意区分java内存模型(JMM)和java内存结构或者叫内存布局的区别。JMM决定一个线程对共享变量的写入时,能对一个线程可见。内存结构见:https://my.oschina.net/uwith/blog/3110227(https://my.oschina.net/uwith/blog/3110227)为什么有线程安全问题?:当多个线程同时共
Wesley13 Wesley13
3年前
Java多线程之内存可见性
Java多线程之内存可见性一、Java内存模型介绍什么是JMM?Java内存模型(JavaMemoryModel)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的细节所有的变量都存储在主内存中每个线程都
Wesley13 Wesley13
3年前
Java 多线程:volatile关键字
概念volatile也是多线程的解决方案之一。\\volatile能够保证可见性,但是不能保证原子性。\\它只能作用于变量,不能作用于方法。当一个变量被声明为volatile的时候,任何对该变量的读写都会绕过高速缓存,直接读取主内存的变量的值。如何理解直接读写主内存的值:回到多线程生成的原因(Java内存模型与
Wesley13 Wesley13
3年前
JAVA内存模型与线程以及volatile理解
Java内存模型是围绕在并发过程中如何处理原子性、可见性、有序性来建立的。一、主内存与工作内存  Java内存模型主要目标是在虚拟机中将变量存储到内存和从内存中取出变量。这里的变量包括:实例字段、静态字段、构成数组对象的元素;不包括局部变量和方法参数,因为它们是线程私有的。Java内存模型规定了所有变量都存储在主内存,线程的工作内
Wesley13 Wesley13
3年前
Java分布式锁看这篇就够了
\什么是锁?在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到