java并发:java内存模型与多线程之volatile

时间:2021-08-09
本文章向大家介绍java并发:java内存模型与多线程之volatile,主要包括java并发:java内存模型与多线程之volatile使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

java内存模型

Java作为平台无关性语言,JSL(java语言规范)定义了一个统一的内存管理模型JMM(Java Memory Model),JMM屏蔽了底层平台内存管理细节。

JMM规定了JVM有主内存(Main Memory)和工作内存(Working Memory)。

主内存

即java堆内存,存放程序中所有的类实例、静态数据等变量,是多个线程共享的。

工作内存

用于存放线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每个线程私有的,其他线程不能访问。

Note:

线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。

于是每个线程对变量的操作都是先从主内存将其拷贝到工作内存,再对其进行操作,多个线程之间不能直接互相传递数据。

示例

在java中,执行下面这个语句:

i=10;

执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中,而不是直接将数值10写入主存当中。

内存不可见问题

可见性指的是在一个线程中修改变量的值以后,在其他线程中能够看到这个值。

Java 内存模型是一个抽象的概念,在实际实现中线程的工作内存是什么呢?

为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。

Java 内存模型的工作内存对应下图中的 Ll 或者 L2 缓存或者 CPU 的寄存器。 

案例

假如线程 A 和线程 B 同时处理一个共享变量 , 会出现什么情况? 

情景假设:

线程 A 和线程 B 使用不同CPU执行,并且当前两级Cache都为空

执行过程:

  • 线程A根据需要获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中 X 的值(假如为 0),并把 X 的值缓存到两级缓存
  • 线程A修改 X 的值为 1,然后将其写入两级 Cache,并刷新到主内存
  • 线程B获取 X 的值,一级缓存没有命中,二级缓存命中了,返回X=1
  • 线程B修改 X 的值为 2,将其存放到线程 2 所在的一级 Cache 和共享二级 Cache 中,最后更新主内存中X的值为2
  • 线程A根据需要获取共享变量X的值,一级缓存命中,其中X=l

到这里问题就出现了,线程 B 已经把 X 的值修改为了 2,线程 A 获取的还是 1;这就是共享变量的内存不可见问题,此处体现为线程 B 写入的值对线程 A 不可见。

据此可知,在java内存模型中,存在缓存一致性问题问题,所以在多线程环境中必须解决可见性问题。

对此Java提供了解决方式:使用关键字 volatile

关键字volatile

volatile是一个特殊的修饰符,只有成员变量才能使用它,具体用法如下:

public volatile static int count=0;//在声明的时候带上volatile关键字即可

与Synchronized及ReentrantLock等提供的互斥相比,Synchronized保证了Synchronized同步块中变量的可见性,而volatile则是保证了所修饰变量的可见性。

详解

关键字volatile,从表面意思上是说这个变量是易变的,不稳定的。

这个关键字的作用是告诉编译器,凡是被该关键字声明的变量都是易变的、不稳定的;所以不要试图对该变量使用缓存等优化机制,而应当每次都从它的内存地址中去读值。

Note:

这里只是说每次读取volatile的变量时都要从它的内存地址中读取,并没有说每次修改完volatile的变量后都要立刻将它的值写回内存

也就是说volatile只提供了内存可见性,而没有提供原子性。

同一个变量多个线程间的可见性与多个线程中操作互斥是两件事情,操作互斥提供了操作整体的原子性,所以说如果用这个关键字做高并发的安全机制的话是不可靠的。

问题:什么时候使用volatile关键字?

根据volatile的特点,其最好用于对内存可见性要求高,而对原子性要求低的地方,譬如:用于修饰作为开关状态的变量。

原文地址:https://www.cnblogs.com/studyLog-share/p/5313219.html