一.谈谈对volatile的理解

volatile是java虚拟机提供的轻量级的同步机制

保证可见性、不保证原子性、禁止指令重排

1.可见性理解:所有线程存放都是主内存的副本(比如某个变量值为25),t1线程的工作内存发生改变(值25改为37),写会主内存中,及时通知其他线程t2,t3更新最新的主内存数据(37),达到数据一致性,这种及时通知其他线程俗称可见性

2.可见性的代码验证

**
* 1验证volatile的可见性
* 1.1 如果int num = 0,number变量没有添加volatile关键字修饰
* 1.2 添加了volatile,可以解决可见性
*/
public class VolatileDemo { public static void main(String[] args) {
visibilityByVolatile();//验证volatile的可见性
} /**
* volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
*/
public static void visibilityByVolatile() {
MyData myData = new MyData(); //第一个线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
//线程暂停3s
TimeUnit.SECONDS.sleep(3);
myData.addToSixty();
System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "thread1").start();
//第二个线程是main线程
while (myData.num == 0) {
//如果myData的num一直为零,main线程一直在这里循环
}
System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);
}
} class MyData {
// int num = 0;
volatile int num = 0; public void addToSixty() {
this.num = 60;
}
}
输出结果: thread1 come in
thread1 update value:60
//线程进入死循环
当我们加上volatile关键字后,volatile int num = 0;输出结果为: thread1 come in
thread1 update value:60
main mission is over, num value is 60
//程序没有死循环,结束执行

2.不保证原子性

原子性:不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败

代码验证

     Mydata mydata = new Mydata();
System.out.println("改变之前,主线程的获取值为:"+mydata.data);
// new Thread(()->{
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// mydata.addSixty();
// System.out.println("线程修改后的,值为:"+mydata.data);
// }).start();
//等待会
// while (mydata.data==0){
//
// }
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
mydata.add();
} System.out.println("线程修改后的,值为:"+mydata.data);
},String.valueOf(i)).start();
}
Thread.sleep(3000);
// while (Thread.activeCount()>2){
// Thread.yield();
// }
System.out.println("20个线程进行++操作后,主线程的获取值为:"+mydata.data);
} } class Mydata{
volatile int data=0; public void addSixty(){
this.data=60;
} public void add(){
this.data++;
}
}

输出打印:

20个线程进行++操作后,主线程的获取值为:19772

发现得到的值不是20000

(1)为什么保证不了原子性

java字节码(https://blog.csdn.net/hao707822882/article/details/26974073)

public void add();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field data:I           ======》从对象中获取字段 
5: iconst_1         ========》将int类型常量1压入栈 
6: iadd                 =====》加
7: putfield #2 // Field data:I  =====》设置对象中字段的值 

10: return

原因:java一行i++操作,在jvm底层需要执行四行字节码指令,线程1可能在改变值后,在往主内存更新时(线程太快了),某个步骤被挂起,线程2,3来写入,导致线程没有更新成功,写丢失

3.如何保证原子性呢

(1)不使用synchronized,使用原子类AtomicInteger进行++操作

4.禁止指令重排序

==多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测==

重排代码实例:

声明变量:int a,b,x,y=0

线程1 线程2
x = a; y = b;
b = 1; a = 2;
结 果 x = 0 y=0

如果编译器对这段程序代码执行重排优化后,可能出现如下情况:

线程1 线程2
b = 1; a = 2;
x= a; y = b;
结 果 x = 2 y=1

这个结果说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的

volatile实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象

==内存屏障==(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:

  1. 保证特定操作的执行顺序
  2. 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在之零件插入一i奥Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排顺序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

3、你在那些地方用过volatile

当普通单例模式在多线程情况下:

public class SingletonDemo {
private static SingletonDemo instance = null; private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 构造方法SingletonDemo()");
} public static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
} public static void main(String[] args) {
//构造方法只会被执行一次
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance()); //并发多线程后,构造方法会在一些情况下执行多次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, "Thread " + i).start();
}
}
}

其构造方法在一些情况下会被执行多次

解决方式:

  1. 单例模式DCL代码

    DCL (Double Check Lock双端检锁机制)在加锁前和加锁后都进行一次判断

        public static SingletonDemo getInstance() {
    if (instance == null) {
    synchronized (SingletonDemo.class) {
    if (instance == null) {
    instance = new SingletonDemo();
    }
    }
    }
    return instance;
    }

    大部分运行结果构造方法只会被执行一次,但指令重排机制会让程序很小的几率出现构造方法被执行多次

    ==DCL(双端检锁)机制不一定线程安全==,原因时有指令重排的存在,加入volatile可以禁止指令重排

    原因是在某一个线程执行到第一次检测,读取到instance不为null时,instance的引用对象可能==没有完成初始化==。instance=new SingleDemo();可以被分为一下三步(伪代码):

    memory = allocate();//1.分配对象内存空间
    instance(memory); //2.初始化对象
    instance = memory; //3.设置instance执行刚分配的内存地址,此时instance!=null

    步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化时允许的,如果3步骤提前于步骤2,但是instance还没有初始化完成

    但是指令重排只会保证串行语义的执行的一致性(单线程),但并不关心多线程间的语义一致性。

    ==所以当一条线程访问instance不为null时,由于instance示例未必已初始化完成,也就造成了线程安全问题。==

  2. 单例模式volatile代码

    为解决以上问题,可以将SingletongDemo实例上加上volatile

    private static volatile SingletonDemo instance = null;

java之volatile的更多相关文章

  1. 【转】java中volatile关键字的含义

    java中volatile关键字的含义   在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  2. Java中Volatile关键字详解

    一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...

  3. 转:java中volatile关键字的含义

    转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  4. Java中Volatile的作用

    Java中Volatile的作用 看了几篇博客,发现没搞懂.可是简单来说,就是在我们的多线程开发中.我们用Volatile关键字来限定某个变量或者属性时,线程在每次使用变量的时候.都会读取变量改动后的 ...

  5. java中volatile不能保证线程安全

    今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而 ...

  6. java中volatile

    volatile用来修饰变量.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volat ...

  7. Java并发-volatile的原理及用法

    Java并发-volatile的原理及用法 volatile属性:可见性.保证有序性.不保证原子性.一.volatile可见性 在Java的内存中所有的变量都存在主内存中,每个线程有单独CPU缓存内存 ...

  8. java中volatile关键字的理解

    一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有 ...

  9. java 使用volatile实现线程数据的共享

    java 使用volatile实现线程数据的共享 直接上代码看效果: public class VolatileTest extends Thread { private volatile boole ...

  10. java 并发——volatile

    java 并发--volatile 介绍 维基百科: volatile 是一个类型修饰符(type specifier).volatile 的作用是确保本条指令不会因编译器的优化而省略,且要求每次直接 ...

随机推荐

  1. 第11.24节 Python 中re模块的其他函数

    一. re.compile函数 正则表达式编译函数,在后面章节专门介绍. 二. re.escape(pattern) re.escape是一个工具函数,用于对字符串pattern中所有可能被视为正则表 ...

  2. webpack项目如何正确打包引入的自定义字体?

    一. 如何在Vue或React项目中使用自定义字体 在开发前端项目时,经常会遇到UI同事希望在项目中使用一个炫酷字体的需求.那么怎么在项目中使用自定义字体呢? 其实实现起来并不复杂,可以借用CSS3 ...

  3. CSP-S 2020 游记

    2020.10.11 初赛了,没怎么做题,之前在网上两次初赛模拟赛 95pts / 94pts,还白嫖了一本书,感觉挺好. 去考场,中途不舒服去了厕所,回来发现有点来不及,阅读程序最后两题不会瞎蒙. ...

  4. WebFlux中thymeleaf视图找不到的问题解决

    由于在weblux临时增加一个H5的页面,发生如下错误 Whitelabel Error Page This application has no configured error view, so ...

  5. 微信小程序-卡券开发(前端)

    刚完成一个微信小程序卡券开发的项目.下面记录开发前,自己困惑的几个问题. 因为我只负责了前端.所以下面主要是前端的工作. 项目概述:按照设计图开发好首页上的优惠券列表,点击某个优惠券,输入手机号,点击 ...

  6. EditPlus各个版本的注册码,亲测可用

    原文链接:https://www.cnblogs.com/shihaiming/p/6422441.html EditPlus4注册码 注册名:host1991    序列号:14F50-CD5C8- ...

  7. 初入Nginx--配置篇

    Nginx的主配置文件为/path/to/nginx/nginx.conf.Nginx.conf的配置文件结构主要由以下几个部分组成: ..... events{ .... } http{ .... ...

  8. js--数组的map()方法的使用

    javaScript中Array.map()的用法 前言 作为一个刚刚踏入前端世界的小白,工作中看到身边同事大佬写的代码就像古诗一样简介整齐,而我的代码如同一堆散沙,看上去毫无段落感,而且简单的功能需 ...

  9. Shiro实现Basic认证

    前言 今天跟小伙伴们分享一个实战内容,使用Spring Boot+Shiro实现一个简单的Http认证. 场景是这样的,我们平时的工作中可能会对外提供一些接口,如果这些接口不做一些安全认证,什么人都可 ...

  10. python 类和方法(面向对象)

    类和方法 name = "Jack" city = "bejing" print("my name is %S and come from %s &q ...