本篇讲述 5 个重点:
- volatile关键字
- java 内存模型(JMM)
- volatile 内存模型可见性
- volatile 工作原理
- volatile原理
谈 volatile之前,回顾一下 java内存模型的三要素:原子性,可见性,有序性,也是并发的三要素。
并发编程三要素
1.原子性:
和数据库的原子性一样,要么全部执行成功,要么全部执行失败回到原始。
简单的读取,赋值(数字赋值给某个变量,变量之间的赋值就不是原子的了)才是原子操作。
比如:i = 2;j = i;i++;i = i + 1;
上面4个操作中,i=2是读取操作,必定是原子性操作,j=i你以为是原子性操作,其实吧,分为两步,一是读取i的值,然后再赋值给j,这就是2步操作了,称不上原子操作,i++和i = i + 1其实是等效的,读取i的值,加1,再写回主存,那就是3步操作了。
所以上面的举例中,最后的值可能出现多种情况,就是因为满足不了原子性。
非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子2操作,java的concurrent包下提供了一些原子类:比如:AtomicInteger、AtomicLong等。
2.可见性
多个线程访问同一个共享变量,其中一个线程对共享变量修改,其他线程能够立即获得修改后的值
3.有序性
编译器和处理器为了优化程序性能而对执行序列经行重排序,也就是你编写的代码顺序和最终指令执行的顺序不是一样的。
指令重排序不会影响单线程的执行,但是会影响多线程并发执行的正确性
volatile
volatile是java语言的类型修饰符,一个共享变量被volatile修饰,就具备了两个含义:
- 保证多线程下的可见性
- 禁止指令进行重排序
注意,注意,volatile只能保证被它修饰的内容具有可见性、有序性。
volatile只能保证单次操作的原子性,i++这种操作不能保证原子性
volatile内存模型
java的内存模型是一个抽象的概念,不真实存在。它描述了一组规则和规范,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。
试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
java 内存模型规定了所有的变量都存贮在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中用到的变量在主内存中的拷贝副本,线程对变量的所有操作都是在工作内存中经行,而不能直接读写主内存。不同的线程之间也无法直接访问对放工作内存中的变量。线程间的变量传递均需要自己的工作内存和主内存之间经行数据同步
- 主内存主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。
- 工作内存每条线程都有自己的工作内存(Working Memory,又称本地内存,可与前面介绍的处理器高速缓存类比),线程的工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。
volatile原理
Volatile 保证内存可见性
主内存和工作内存之间的交互有具体的交互协议,JMM定义了八种操作来完成,这八种操作是原子的、不可再分的,它们分别是:lock,unlock,read,load,use,assign,store,write,其中lock,unlock,read,write作用于主内存;load,use,assign,store作用于工作内存。
(1) lock:将主内存中的变量锁定,为一个线程所独占
(2) unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量
(3) read:将主内存中的变量值读到工作内存当中
(4) load:将read读取的值保存到工作内存中的变量副本中。
(5) use:将值传递给线程的代码执行引擎
(6) assign:将执行引擎处理返回的值重新赋值给变量副本
(7) store:将变量副本的值存储到主内存中。
(8) write:将store存储的值写入到主内存的共享变量当中。
- 从主存复制变量到当前工作内存(read and load)
- 执行代码,改变共享变量值 (use and assign)
- 用工作内存数据刷新主存相关内容 (store and write)
指令规则
- read 和 load、store和write必须成对出现
- assign操作,工作内存变量改变后必须刷回主内存
- 同一时间只能运行一个线程对变量进行lock,当前线程lock可重入,unlock次数必须等于lock的次数,该变量才能解锁。
- 对一个变量lock后,会清空该线程工作内存变量的值,重新执行load或者assign操作初始化工作内存中变量的值。
- unlock前,必须将变量同步到主内存(store/write操作)
Volatile源码案例
public class VolatileDamo extends Thread{
private volatile boolean isRuning = true;
private void setRuning(boolean isRuning){
this.isRuning = isRuning;
}
@Override
public void run(){
System.out.println("进入run方法...");
while(isRuning){
}
System.out.println("线程停止!!!");
}
public static void main(String[] args) throws InterruptedException{
VolatileDamo damo = new VolatileDamo();
damo.start();
Thread.sleep(1000);
damo.setRuning(false);
System.out.println("isRuning值已经设置为false...");
}
有好的补充、建议和疑问,欢迎留言讨论