一.谈谈对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. 手写Json解析器学习心得

    一. 介绍 一周前,老同学阿立给我转了一篇知乎回答,答主说检验一门语言是否掌握的标准是实现一个Json解析器,网易游戏过去的Python入门培训作业之一就是五天时间实现一个Json解析器. 知乎回答- ...

  2. 关于EF框架EntityState的几种状态

    在使用EF框架时,我们通常都是通过调用SaveChanges方法把增加/修改/删除的数据提交到数据库,但是上下文是如何知道实体对象是增加.修改还是删除呢?答案是通过EntityState的枚举值来判断 ...

  3. 2020武汉dotNET俱乐部分享交流圆满结束

    经过长达2个多月的准备,终于在12月5日圆满的举行了武汉首届dotNET俱乐部线下分享交流活动.我们一共精心准备了3个目前比较热门的主题,分别如下: Jason分享的<ABP开发框架的扩展应用& ...

  4. 团队作业part4--项目冲刺

    七天敏捷冲刺汇总 1. Day1 Scrum 冲刺博客 2. Day2 Scrum 冲刺博客 3. Day3 Scrum 冲刺博客 4. Day4 Scrum 冲刺博客 5. Day5 Scrum 冲 ...

  5. js 实现字符串翻转

    字符串作在程序中是非常常见的,因为程序中绝大部分的数据都可以当作字符串来处理.在这里介绍几种翻转字符串的方法. (1)使用字符串函数 //使用数组翻转函数 function reverseString ...

  6. AtCoder Regular Contest 109

    Contest Link 为什么还没有 Official Editorial 啊--哦,原来是日文题解,那没事了. A - Hands 有两幢 100 层的楼房 \(A,B\) ,将地面所在的楼层称为 ...

  7. 阿里云OSS生成sts令牌

    业务场景: 如果前端直接上传文件到OSS,势必要暴露令牌,无法精准控制上传内容等,使用临时令牌即可解决这个问题. 先去阿里云后台设置好token,角色,地区等 pom.xml <dependen ...

  8. c++笔试题3

    一.[阿里C++面试题]1.如何初始化一个指针数组.答案: 错题解析:首先明确一个概念,就是指向数组的指针,和存放指针的数组. 指向数组的指针:char (*array)[5];含义是一个指向存放5个 ...

  9. 前置机器学习(四):一文掌握Pandas用法

    Pandas提供快速,灵活和富于表现力的数据结构,是强大的数据分析Python库. 本文收录于机器学习前置教程系列. 一.Series和DataFrame Pandas建立在NumPy之上,更多Num ...

  10. 使用Swiper快速实现3D效果轮播

    最近经常接到轮播图3D效果的需求, 特在此记录一下以备之后使用. 具体实现效果如下: 在这里介绍两种使用方式, 一种原生的html+php后端渲染, 一种是使用vue. 原生实现 引入 首先我们介绍原 ...