一.谈谈对volatile的理解

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

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

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

2.可见性的代码验证

  1. **
  2. * 1验证volatile的可见性
  3. * 1.1 如果int num = 0number变量没有添加volatile关键字修饰
  4. * 1.2 添加了volatile,可以解决可见性
  5. */
  6. public class VolatileDemo {
  7.  
  8. public static void main(String[] args) {
  9. visibilityByVolatile();//验证volatile的可见性
  10. }
  11.  
  12. /**
  13. * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
  14. */
  15. public static void visibilityByVolatile() {
  16. MyData myData = new MyData();
  17.  
  18. //第一个线程
  19. new Thread(() -> {
  20. System.out.println(Thread.currentThread().getName() + "\t come in");
  21. try {
  22. //线程暂停3s
  23. TimeUnit.SECONDS.sleep(3);
  24. myData.addToSixty();
  25. System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);
  26. } catch (Exception e) {
  27. // TODO Auto-generated catch block
  28. e.printStackTrace();
  29. }
  30. }, "thread1").start();
  31. //第二个线程是main线程
  32. while (myData.num == 0) {
  33. //如果myData的num一直为零,main线程一直在这里循环
  34. }
  35. System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);
  36. }
  37. }
  38.  
  39. class MyData {
  40. // int num = 0;
  41. volatile int num = 0;
  42.  
  43. public void addToSixty() {
  44. this.num = 60;
  45. }
  46. }
  47. 输出结果:
  48.  
  49. thread1 come in
  50. thread1 update value:60
  51. //线程进入死循环
  52. 当我们加上volatile关键字后,volatile int num = 0;输出结果为:
  53.  
  54. thread1 come in
  55. thread1 update value:60
  56. main mission is over, num value is 60
  57. //程序没有死循环,结束执行

2.不保证原子性

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

代码验证

  1. Mydata mydata = new Mydata();
  2. System.out.println("改变之前,主线程的获取值为:"+mydata.data);
  3. // new Thread(()->{
  4. // try {
  5. // Thread.sleep(3000);
  6. // } catch (InterruptedException e) {
  7. // e.printStackTrace();
  8. // }
  9. // mydata.addSixty();
  10. // System.out.println("线程修改后的,值为:"+mydata.data);
  11. // }).start();
  12. //等待会
  13. // while (mydata.data==0){
  14. //
  15. // }
  16. for (int i = 0; i < 20; i++) {
  17. new Thread(()->{
  18. for (int i1 = 0; i1 < 1000; i1++) {
  19. mydata.add();
  20. }
  21.  
  22. System.out.println("线程修改后的,值为:"+mydata.data);
  23. },String.valueOf(i)).start();
  24. }
  25. Thread.sleep(3000);
  26. // while (Thread.activeCount()>2){
  27. // Thread.yield();
  28. // }
  29. System.out.println("20个线程进行++操作后,主线程的获取值为:"+mydata.data);
  30. }
  31.  
  32. }
  33.  
  34. class Mydata{
  35. volatile int data=0;
  36.  
  37. public void addSixty(){
  38. this.data=60;
  39. }
  40.  
  41. public void add(){
  42. this.data++;
  43. }
  44. }

输出打印:

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

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

  1. public class SingletonDemo {
  2. private static SingletonDemo instance = null;
  3.  
  4. private SingletonDemo() {
  5. System.out.println(Thread.currentThread().getName() + "\t 构造方法SingletonDemo()");
  6. }
  7.  
  8. public static SingletonDemo getInstance() {
  9. if (instance == null) {
  10. instance = new SingletonDemo();
  11. }
  12. return instance;
  13. }
  14.  
  15. public static void main(String[] args) {
  16. //构造方法只会被执行一次
  17. // System.out.println(getInstance() == getInstance());
  18. // System.out.println(getInstance() == getInstance());
  19. // System.out.println(getInstance() == getInstance());
  20.  
  21. //并发多线程后,构造方法会在一些情况下执行多次
  22. for (int i = 0; i < 10; i++) {
  23. new Thread(() -> {
  24. SingletonDemo.getInstance();
  25. }, "Thread " + i).start();
  26. }
  27. }
  28. }

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

解决方式:

  1. 单例模式DCL代码

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

    1. public static SingletonDemo getInstance() {
    2. if (instance == null) {
    3. synchronized (SingletonDemo.class) {
    4. if (instance == null) {
    5. instance = new SingletonDemo();
    6. }
    7. }
    8. }
    9. return instance;
    10. }

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

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

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

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

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

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

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

  2. 单例模式volatile代码

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

    1. 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. metasploit魔鬼训练营靶机环境搭建(第二章)

    环境搭建,书上已经很详细了,路由转发的那个鼓捣了好久都没弄好,菜的啊 所以先往书后面继续学习,不停留在配置环境上了. backtrack没有下载,使用的kali linux 其他的都是一样的 百度网盘 ...

  2. 小程序使用动画时的 px 单位 转 rpx的方法

    借助API wx.getSystemInfoSync(); 通过API可获取的值: // 在 iPhone6 下运行: var systemInfo = wx.getSystemInfoSync(); ...

  3. 基于gin的golang web开发:服务间调用

    微服务开发中服务间调用的主流方式有两种HTTP.RPC,HTTP相对来说比较简单.本文将使用 Resty 包来实现基于HTTP的微服务调用. Resty简介 Resty 是一个简单的HTTP和REST ...

  4. jQuery插件的2种类型

    1.封装方法插件  封装方法插件在本质上来说,是一个对象级别的插件,这类插件首先通过jQuery选择器获取对象,并为对象添加方法,然后,将方法进行打包,封闭成一个插件,这种类型的插件编写简单,极易调用 ...

  5. 别再说你不懂什么是API了

    API 全称 Application Programming Interface, 即应用程序编程接口. 看到这里,急性子的小白同学马上就憋不住了:这不管是英文还是中文我每个字都懂啊,只是凑一块就不知 ...

  6. 20201126-1 txt文件筛选与读写【】

    Exercise 1import os # 设置文件夹路径为'工作文件夹',获取文件夹下的所有文件和文件夹名称 path = './工作文件夹/' files_list = os.listdir(pa ...

  7. android adb命令* daemon not running.starting it now on port 5037 * 问题解决

    输入adb devices却出现了问题daemon not running.starting it now on port 5037, 2. 原因: adb的端口(5037)被占用了.至于这个5037 ...

  8. Greenplum 性能优化之路 --(一)分区表

    一.什么是分区表 分区表就是将一个大表在物理上分割成若干小表,并且整个过程对用户是透明的,也就是用户的所有操作仍然是作用在大表上,不需要关心数据实际上落在哪张小表里面.Greenplum 中分区表的原 ...

  9. Feign String 参数 传递null 以及 空字符串问题

    笔记链接:https://app.yinxiang.com/fx/c82f6d74-3432-4703-83c8-5175f5986f97 备注 因为笔记在印象笔记上进行编辑,而且为Markdown格 ...

  10. java_day_02

    一.return的两个作用 1.停止当前方法 2.将后面的结果数据返回值还给调用处 二.方法的三种调用格式 1.单独调用:方法名(参数): public class Method { public s ...