谈谈你对volatile的理解
1.volatile是Java虚拟机提供的轻量级的同步机制
1.1保证可见性
1.2不保证原子性
1.3禁止指令重排
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.
JMM关于同步规定:
1.线程解锁前,必须把共享变量的值刷新回主内存
2.线程加锁前,必须读取主内存的最新值到自己的工作内存
3.加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:

2.1可见性
通过前面对JMM的介绍,我们知道
各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存操作后再写回主内存中的.
这就可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时 ,另外一个线程BBB又对内存中的一个共享变量X进行操作,但此时A线程工作内存中的共享变量X对线程B来说并不可见.这种工作内存与主内存同步延迟现象就造成了可见性问题.volatile可以保证可见性,及时通知其它线程,主物理内存的值已经被修改
case
class MyData {
int num = 0;
// volatile int num = 0;
public void addTo60() {
this.num = 60;
}
}
/**
* 1、验证volatile的可见性
* 1.1假如int num=0; num变量之前没有添加volatile关键字修饰,main线程死循环等待,程序无法结束
* 1.2 num变量之前添加volatile关键字修饰,及时通知其它线程,main线程感知到修改,结束程序
*/
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
// 线程AAA
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 进来了...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + " 修改num为:" + myData.num);
}, "AAA").start();
while (myData.num == 0) {
}
// main线程
System.out.println(Thread.currentThread().getName() + "感知到变量被修改...");
}
}
2.2原子性
case
class MyData {
volatile int num = 0;
public void addTo60() {
this.num = 60;
}
public void addAdd() {
this.num++;
}
}
/**
* 2、验证volatile不保证原子性
* * 2.1 不保证原子性案例
*/
public static void main(String[] args) {
// seeOkByVolatile();
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addAdd();
}
}, String.valueOf(i)).start();
}
// 需要等待上面完成全部计算,看看main线程最后得到的结果是多少
while (Thread.activeCount() > 2) {
Thread.yield(); // 如果线程数大于2,就让出执行权.这里一个main线程,一个是后台GC线程
}
System.out.println(Thread.currentThread().getName() + "int type finally num = " + myData.num);
}
num++在多线程下是非线程安全的,如何不加synchronized解决?

2.3解决原子性问题
使用java的JUC并发包下的原子操作类,其原理见CAS
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int num = 0;
public void addTo60() {
this.num = 60;
}
public void addAdd() {
this.num++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic() {
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addAdd();
myData.addMyAtomic();
}
}, String.valueOf(i)).start();
}
// 需要等待上面完成全部计算,看看main线程最后得到的结果是多少
while (Thread.activeCount() > 2) {
Thread.yield(); // 如果线程数大于2,就让出执行权.这里一个main线程,一个是后台GC线程
}
System.out.println(Thread.currentThread().getName() + "int type finally num = " + myData.num);
System.out.println(Thread.currentThread().getName() + "int type finally num = " + myData.atomicInteger);
}
}
2.4有序性
计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3种

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.
处理器在进行重新排序是必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测
重排1
public void mySort(){
int x=11;//语句1
int y=12;//语句2
x=x+5;//语句3
y=x*x;//语句4
}
1234
2134
1324
问题:
请问语句4 可以重排后变成第一条码?
存在数据的依赖性 没办法排到第一个
重排2

案例



3.你在哪些地方用到过volatile?
3.1 单例模式DCL代码
public class SingletonDemo {
private static volatile SingletonDemo instance=null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 构造方法");
}
/**
* 双重检测机制
* @return
*/
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 1; i <=10; i++) {
new Thread(() ->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
3.2代理模式volatile分析
DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化.
instance=new SingletonDem(); 可以分为以下步骤(伪代码)
memory=allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.
memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题.
谈谈你对volatile的理解的更多相关文章
- java面试-谈谈你对volatile的理解
一.volatile特性: volatile是Java虚拟机提供的轻量级的同步机制.主要有三大特性: 保证可见性 不保证原子性 禁止指令重排序 1.保证可见性 1)代码演示 AAA线程修改变量numb ...
- 对volatile的理解--从JMM以及单例模式剖析
请谈谈你对volatile的理解 1.volitale是Java虚拟机提供的一种轻量级的同步机制 三大特性1.1保证可见性 1.2不保证原子性 1.3禁止指令重排 首先保证可见性 1.1 可见性 概念 ...
- Java线程工作内存与主内存变量交换过程及volatile关键字理解
Java线程工作内存与主内存变量交换过程及volatile关键字理解 1. Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行.此处的所谓内存模 ...
- 谈谈嵌套for循环的理解
谈谈嵌套for循环的理解 说for的嵌套,先说一下一个for循环的是怎么用的. 这次的目的是为了用for循环输出一个乘法口诀表,一下就是我的一步步理解. 一. 语法: ...
- volatile变量理解 via《Java并发编程实战》
第3章:对象的共享 volatile关键字的理解 volatile变量,用来确保将变量的更行操作通知到其他线程.当变量申明为volatile类型后,编译器与运行时都会注意带这个变量时共享的,因此不会将 ...
- JVM(一),谈谈你对java的理解
一.谈谈你对java的理解 1.Java特性 (1)平台无关性 一次编译到处运行 (2)GC 垃圾回收机制 (3)语言特性 泛型-反射机制-lambda表达式 (4)面向对象 面向对象语言-三大特性( ...
- 【面试普通人VS高手系列】谈谈你对AQS的理解
AQS是AbstractQueuedSynchronizer的简称,是并发编程中比较核心的组件. 在很多大厂的面试中,面试官对于并发编程的考核要求相对较高,简单来说,如果你不懂并发编程,那么你很难通过 ...
- 【Java面试】面试遇到宽泛的问题,这么回答就稳了,谈谈你对Redis的理解
"谈谈你对Redis的理解"! 面试的时候遇到这类比较宽泛的问题,是不是很抓狂? 是不是不知道从何开始说起? 没关系,今天我用3分钟教你怎么回答. 大家好,我是Mic,一个工作了1 ...
- 谈谈对Spring IOC的理解(转)
学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...
随机推荐
- 树形DP 枚举祖宗的例题
这类题目是真的很头疼....其实这类题目的特征也很明显,叶子结点贡献答案时和其所在链的祖宗有关,也就是说要想得知其贡献必须知道他的所有祖宗的贡献,其实处理方法也不是太难,就是在dfs枚举时顺便把祖宗的 ...
- Spring Cloud 微服务实战——nacos 服务注册中心搭建(附源码)
作为微服务的基础功能之一的注册中心担任重要的角色.微服务将单体的服务拆分成不同的模块下的服务,而不同的模块的服务如果进行通信调用呢?这就需要服务注册与发现.本文将使用阿里开源项目 nacos 搭建服务 ...
- Socket `accept queue is full ` 但是一个连接需要从SYN->ACCEPT
由于标题长度有限制,我把想要描述的问题再次描述下: 内核通常会为每一个LISTEN状态的Socket维护两个队列: 1 accept队列: listen()函数第二个参数BACKLOG指定,表示已完成 ...
- prometheus(6)之常用服务监控
监控常用服务 1.tomcat 2.redis 3.mysql 4.nginx 5.mongodb prometheus监控tomcat tomcat_exporter地址 https://githu ...
- 学习JS的第一天--初识JS
1.初识JS a.我的第一个JS程序: document.write("Hello JS")://这段代码是输出到body中就是直接打开就可以看到: console.log(&qu ...
- go的常用数据类型-持续优化篇
p.p1 { margin: 0; font: 12px "Helvetica Neue"; color: rgba(69, 69, 69, 1) } p.p2 { margin: ...
- 限制q-error,防止产生次优计划
原文:<Preventing bad plans by bounding the impact of cardinality estimation errors> 摘要 文章定义了一个衡量 ...
- Swift-技巧(五)设置圆角的代码
摘要 实现控件圆角的代码时,会不假思索的写 cornerRadius 和 masksToBounds,因为搜索得到的设置圆角的代码就是这样.今天突发奇想,为什么要写 masksToBounds? 打个 ...
- CodeBlocks调试器缺少(gdb.exe)文件
错误如下: Building to ensure sources are up-to-date Selecting target: Debug ERROR: You need to specify ...
- [hdu6601]Keen On Everything But Triangle
有两个结论:1.排序后,答案一定是连续的三个数:2.当序列长度超过44一定有三个相同的数(因为即使该序列是斐波那契数列,此时也超过了1e9),然后用主席树等数据结构(略卡常,建议主席树)来维护前45大 ...