一.谈谈对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.22节 Python 中re模块的字符串分割器:split函数

    一. 引言 在<第11.2节 Python 正则表达式支持函数概览>介绍了re模块的主要函数,在<第11.3节 Python正则表达式搜索支持函数search.match.fullm ...

  2. 网鼎杯 fakebook

    这道题目登录之后我们可以看到有join和login login即登录,join即注册 我们通过查看robots.txt可以知道 有源代码泄露. 先将泄露的源码下载下来审计一波 <?php cla ...

  3. Leetcode学习笔记(1)

    scrapy爬虫的学习告一段落,又因为现在在学习数据结构,做题平台是lettcode:https://leetcode-cn.com/ 每周都要交一次做题的笔记,所以把相关代码和思路同时放在博客上记录 ...

  4. 转载 HTTP协议

    转载自:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, ...

  5. 团队作业part1--团队展示&选题

    一.团队展示 1.队名 DiligentVegetableChicken 2.队员信息 纪昂学(组长):3118005053 廖业成:3118005060 蔡越:3118005086 周梓波:3118 ...

  6. 题解-CF348E Pilgrims

    题面 CF348E Pilgrims 有一棵 \(n\) 个点的 带权 树和 \(m\) 个关键点,要求杀了一个不关键的点,满足最多的关键点到离它最远的所有关键点的路径都被打断.求可以满足的最多关键点 ...

  7. 数据结构—— Trie (前缀树)

    实现一个 Trie (前缀树),包含 插入, 查询, 和 查询前缀这三个操作. Trie trie = new Trie(); trie.insert("apple"); trie ...

  8. SseEmitter推送

    后端代码SseController.java package com.theorydance.mywebsocket.server; import java.util.HashMap; import ...

  9. 【Electron Playground 系列】窗口篇

    作者:Kurosaki 本文主要讲解Electron 窗口的 API 和一些在开发之中遇到的问题. 官方文档 虽然比较全面,但是要想开发一个商用级别的桌面应用必须对整个 Electron API  有 ...

  10. 彻底理解Hive中的锁

    前面遇到过一次因为Hive中表被锁住了,导致定时任务一直失败.这两天又出现了表被锁,原因是连接hiveserver2过于频繁,mysql连接被打满,引发的连锁反应,导致我们的小时任务一直失败,下午重点 ...