java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)
Posted 李小立Flag
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)相关的知识,希望对你有一定的参考价值。
i++是否线程安全?
在了解volatile之前,我们先思考一个问题,你认为i++是线程安全的吗?为什么呢?,带着这样一个思考,我们进入下文。
首先我们来了解一下内存模型。
内存模型:
Java虚拟机规范试图定义一种Java内存模型(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,JMM规定所有变量都是存在主存中的,每个线程又包含自己的工作内存,线程中的所有的操作都是以工作内存为主,它们只能访问自己的工作内存,且操作后都要把值在同步回主内存。
//假设i=1
i=i+1;
根据上图可知,当某个线程执行这个语句时,先从主存读取i的值,在load进工作内存中,交给cpu处理,在进行+1操作后计算结果为2,将2写入工作内存中,再将值写入住内存。
这个代码在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。
两个线程中执行各执行一次i++结果会等于三吗。
假设i=1,在单线程中执行两次i++结果肯定等于三,但在多线程中确是不一定的,由于多线程中存在线程的上下文切换。
我们假设:
- thread1在读取主存的值i=1 写入工作内存1,然后进行+1的操作,将i=2写入工作内存1,假如此时发生线程的切换切换到thread2(主内存的值任然是i=1,工作内存1中的i=2并没有写入主存);
- thread2在读取主存中的i=1,然后在进行+1的操作,将i=2写入工作内存2中,然后再将2写入主内存中,线程切换到thread1
- thread1 将工作内存1的i=2写入主内存中。
发生了两次i++结果是2,现在我们一定明白了,i++的可能导致线程的不安全。
volatile关键字
简介:volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。
并发编程的三个基本概念
- 原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
- 可见性: 指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- 有序性: 即程序执行的顺序按照代码的先后顺序执行。
volatile的特性(保证可见性和有序性)
(一) 保证可见性,不保证原子性
-
一个被volatile标记变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
-
这个写会操作会导致其他线程中的volatile变量缓存无效。
(二) 禁止指令重排 保证有序性
重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
- 重排序操作不会对存在数据依赖关系的操作进行重排序。
假设:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
- 重排序是为了优化性能,但是不管怎么重排序,要保证单线程下程序的执行结果不能被改变
volatile不能保证原子性
由于i++操作不是一个原子性的操作volatile并不能保证线程安全 。
根据上述分析可将i++拆分为三步操作
- (读) 从主内存中读取i=1的值到工作内存。
- (算) cpu计算i=1+1=2;
- (写) 将i=2写入工作内存,写入主内存。
i++的三步操作不是一个原子操作,每一个步骤都可能发生线程的切换, 只能用synchronized或着Reentrantlock来保证原子性
volatile的原理
在JVM底层volatile是采用“内存屏障”来实现的。加入volatile关键字时,汇编码会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏)。
内存屏障的功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
为什么给DCL(双重检查锁单例模式)加上volatile关键字
public class DoubleCheckLockSingleton {
private DoubleCheckLockSingleton() {}
private static volatile DoubleCheckLockSingleton instance=null;
public static DoubleCheckLockSingleton getInstance(){
if(instance==null){ //标记1
synchronized (DoubleCheckLockSingleton.class){
if(instance==null){
instance=new DoubleCheckLockSingleton(); //标记2
}
}
}
return instance; //标记3
}
先说结论,如果不加volatile 在极端情况下可能发生获取instance实例未初始化的情况。
在执行标记2时可能发生指令重排序,instance=new DoubleCheckLockSingleton();
对象的创建过程
a. 申请内存空间
b. 给申请的内存赋值,初始化instance对象
c. instance指向内存的地址
防止指令重排序
上述的对象创建过程,可能出现指令重排序a-b-c变为a-c-b。
在多线程情况下会有可能出现问题
- 线程1执行到标记2代码时,此时假设线程1的new对象发生指令重排序先执行了a-c 没有执行b 现在cpu发生调度切换到线程2
- 线程2执行到标记1代码,因为instance已经指向内存地址,故instance==null 判断为false不成立(但此时instance实际没有被初始化)。
- 目前线程1的b操作b依然没有执行。
- 线程2继续会执行标记3的代码,返回一个没有被初始化的对象。
- 线程2继续操作这个对象会存在问题。
加上volatile关键字防止指令重拍 保证按照a-b-c的顺序执行(理论上存在此问题,实际情况不太可能发生)。
版权声明:本文为博主原创文章,未经博主允许不得转载
https://blog.csdn.net/qq_44614710/article/details/120178407
以上是关于java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)的主要内容,如果未能解决你的问题,请参考以下文章
java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)