谈谈你对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这两个概念是模糊不清的,是很难理解的,今天和大家 ...
随机推荐
- 第01课 OpenGL窗口(4)
下面的代码处理所有的窗口消息.当我们注册好窗口类之后,程序跳转到这部分代码处理窗口消息. LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄 UINT uMsg ...
- 跟着老猫来搞GO,基础进阶
回顾一下上一篇博客,主要是和大家分享了GO语言的基础语法,其中包含变量定义,基本类型,条件语句,循环语句.那本篇呢就开始和大家同步一下GO语言基础的进阶. 函数的定义 上次其实在很多的DEMO中已经写 ...
- C#与dotNET项目想要另存为一个新项目sln文件丢了怎么办
如下图所示,我想要另存一个工程,把 V4.4整个的项目另存为V4.5,我可以把解决方案文件(.sln)改名字,但是我没法把文件夹改名字,改了打开sln就说找不到. 很简单的一个思路是反正sln是多余的 ...
- 菜鸡的Java笔记第二 - java 数据类型
1.程序的本质实际上就是在于数据的处理上. JAVA中的数据类型有两类 基本数据类型:是进行内容的操作而不是内存的操作 数值型: 整型:byte(-128 ~ 127),short(-32768 ~ ...
- 菜鸡的Java笔记 第二十五 wrapperClass 包装类
wrapperClass 包装类 1.包装类的特点 2.装箱与拆箱操作 3.数据转型处理 内容 Object 类可以接收 ...
- typing使用
官方文档: typing 注: typing是python3.5(PEP484)开始的 可用于: 类型检查器.集成开发环境.静态检查器等, 但是不强制使用 使用方式 两种使用方式 别名: 先定义=, ...
- vue.js学习与实战笔记(2)
驼峰式写法时需要注意的问题 学习到组件这一章时,由于没注意到vue中对于camelCased的解释,导致出错了都找不出来,后面发现 在使用驼峰式写法时,在使用模板的时候需要使用kebab-case命名 ...
- [loj3146]路灯
显然,能从$l$到$r$当且仅当$[l,r)$中的灯全部都亮,以下不妨令询问的$r$全部减1 当修改节点$x$时,找到包含$x$的极大的灯(除$x$以外)全部都亮的区间$[l,r]$,即令$l_{0} ...
- synchronized的一些理解
1. 两个普通的synchronized package ThreadTest; import java.util.concurrent.TimeUnit; public class ThreadTe ...
- LoadRunner 2020 社区版本负载机(Load generator)Linux 安装教程
1.HP官方注册 下载 LoadRunner_2020_Edition_Standalone_Applications_Micro_Focus_LoadRunner_2020_Community_Ed ...