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的理解的更多相关文章

  1. java面试-谈谈你对volatile的理解

    一.volatile特性: volatile是Java虚拟机提供的轻量级的同步机制.主要有三大特性: 保证可见性 不保证原子性 禁止指令重排序 1.保证可见性 1)代码演示 AAA线程修改变量numb ...

  2. 对volatile的理解--从JMM以及单例模式剖析

    请谈谈你对volatile的理解 1.volitale是Java虚拟机提供的一种轻量级的同步机制 三大特性1.1保证可见性 1.2不保证原子性 1.3禁止指令重排 首先保证可见性 1.1 可见性 概念 ...

  3. Java线程工作内存与主内存变量交换过程及volatile关键字理解

    Java线程工作内存与主内存变量交换过程及volatile关键字理解 1. Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行.此处的所谓内存模 ...

  4. 谈谈嵌套for循环的理解

    谈谈嵌套for循环的理解     说for的嵌套,先说一下一个for循环的是怎么用的.      这次的目的是为了用for循环输出一个乘法口诀表,一下就是我的一步步理解.    一.   语法:   ...

  5. volatile变量理解 via《Java并发编程实战》

    第3章:对象的共享 volatile关键字的理解 volatile变量,用来确保将变量的更行操作通知到其他线程.当变量申明为volatile类型后,编译器与运行时都会注意带这个变量时共享的,因此不会将 ...

  6. JVM(一),谈谈你对java的理解

    一.谈谈你对java的理解 1.Java特性 (1)平台无关性 一次编译到处运行 (2)GC 垃圾回收机制 (3)语言特性 泛型-反射机制-lambda表达式 (4)面向对象 面向对象语言-三大特性( ...

  7. 【面试普通人VS高手系列】谈谈你对AQS的理解

    AQS是AbstractQueuedSynchronizer的简称,是并发编程中比较核心的组件. 在很多大厂的面试中,面试官对于并发编程的考核要求相对较高,简单来说,如果你不懂并发编程,那么你很难通过 ...

  8. 【Java面试】面试遇到宽泛的问题,这么回答就稳了,谈谈你对Redis的理解

    "谈谈你对Redis的理解"! 面试的时候遇到这类比较宽泛的问题,是不是很抓狂? 是不是不知道从何开始说起? 没关系,今天我用3分钟教你怎么回答. 大家好,我是Mic,一个工作了1 ...

  9. 谈谈对Spring IOC的理解(转)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

随机推荐

  1. Luogu P1023 [NOIp2000提高组]税收与补贴问题 | 数学

    题目链接 思路:列不等式组,然后解出不等式,得出答案的取值范围,最后取一个绝对值最小的答案就行了. #include<iostream> #include<cstdio> #i ...

  2. Spring事务不生效问题

    事务未生效可能造成严重的数据不一致性问题,因而保证事务生效至关重要.Spring事务是通过Spring aop实现的,所以不生效的本质问题是spring aop没生效,或者说没有代理成功,所以有必要了 ...

  3. 《手把手教你》系列技巧篇(三十七)-java+ selenium自动化测试-日历时间控件-上篇(详解教程)

    1.简介 我们在实际工作中,有可能遇到有些web产品,网页上有一些时间选择,然后支持按照不同时间段范围去筛选数据.网页上日历控件一般,是一个文本输入框,鼠标点击,就会弹出日历界面,可以选择具体日期.这 ...

  4. (三)lamp环境搭建之编译安装php

    1,PRC (People's republic of China) timezone中设置的时间为中国时间. 2,php的官方镜像源,使用linux时可以直接下载的 http://cn2.php.n ...

  5. 编译安装与gcc编译器

    先说一下gcc编译器,只知道这是一个老款的编译器.编译的目的也比较重要,就是将c语言的代码编译成可以执行的binary文件. gcc 简单使用(后期补充) eg: gcc example.c    # ...

  6. 剖析虚幻渲染体系(12)- 移动端专题Part 1(UE移动端渲染分析)

    目录 12.1 本篇概述 12.1.1 移动设备的特点 12.2 UE移动端渲染特性 12.2.1 Feature Level 12.2.2 Deferred Shading 12.2.3 Groun ...

  7. python调试出现报错:SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xb0 in position 9: invalid start byte

    原因:如图,代码里字符串里加上汉字就会报相关错误: 解决办法:开头加上 #-*-coding:GBK -*-   即可解决

  8. 合并代码操作 | git fetch 与 git pull

    前言 首先我们要说简单说git的运行机制.git分为本地仓库和远程仓库,我们一般情况都是写完代码,commit到本地仓库(生成本地仓的commit ID,代表当前提交代码的版本号),然后push到远程 ...

  9. PTA 树的同构 (25分)

    PTA 树的同构 (25分) 输入格式: 输入给出2棵二叉树树的信息.对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号):随后N行,第i行对应编号第 ...

  10. Go语言核心36讲(Go语言实战与应用三)--学习笔记

    25 | 更多的测试手法 在本篇文章,我会继续为你讲解更多更高级的测试方法.这会涉及testing包中更多的 API.go test命令支持的,更多标记更加复杂的测试结果,以及测试覆盖度分析等等. 前 ...