Java多线程volatile原理及用法解析

 更新时间:2020年7月26日 08:31  点击:1641

首先volatile有两大功能:

保证线程可见性

禁止指令重排序

1、保证线程可见性

首先我们来看这样一个程序,其中不加volatile关键字运行的结果截然不同,加上volatile程序能够正常结束,不加则程序进入死循环;

package com.designmodal.design.juc01;

import java.util.concurrent.TimeUnit;

/**
 * @author D-L
 * @Classname T001_volatile
 * @Version 1.0
 * @Description volatile 保证线程的可见性
 * @Date 2020/7/19 17:30
 */
public class T001_volatile {
  //定义一个变量running
  volatile boolean running = true;

  public void m(){
    while(running){
      //TODO 不做任何的处理
      System.out.println("while is running When can I stop -------------");
    }
    System.out.println("method is end ---------------");
  }

  public static void main(String[] args) {
    T001_volatile t001_volatile = new T001_volatile();
    new Thread(t001_volatile::m , "Thread t1").start();

    //停一秒
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //修改running的值
    t001_volatile.running = false;
  }
}

通过上面的小程序说明volatile是具有保证线程之间的可见性的功能的,具体是如何实现的呢?下面给大家解释一下:

之前在上一篇讲synchronized时提到了 堆内存是线程共享的,而线程在工作时有自己的工作内存,对于共享变量running来说,线程1和线程2在运行的时候先把running变量copy到自己工作内存,对这个变量的改变都是在自己的工作内存中,并不会直接的反映到其他线程,如果加了volatile,running变量改变其他线程很快就会知道,这就是线程的可见性;

这里用到的是:MESI(CPU缓存一致性协议) MESI的主要思想:当CPU写数据时,如果该变量是共享数据,给其他CPU发送信号,使得其他的CPU中的该变量的缓存行无效;归根结底这里需要借助硬件来帮助我们。

volatile保证线程可见性但是不能代替synchronized:

package com.designmodal.design.juc01;

import java.util.ArrayList;
import java.util.List;

/**
 * @author D-L
 * @Classname VolatileAndSynchronized
 * @Version 1.0
 * @Description synchronized can not be replaced by volatile
 *        volatile 不能代替synchronized
 *        只能保证可见性 不能保证原子性
 *        count++ 不是原子性操作
 * @Date 2020/xx/xx 23:25
 */
public class VolatileAndSynchronized {
  volatile int count = 0;
  public synchronized void m(){
    for (int i = 0; i < 1000; i++) {
      //非原子性操作 汇编指令至少有三条
      count++;
    }
  }

  public static void main(String[] args) {
    VolatileAndSynchronized v = new VolatileAndSynchronized();
    List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      threads.add(new Thread(v::m , "Thread"+ i));
    }
    threads.forEach(o ->o.start());
    threads.forEach(o ->{
      try {
        o.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
    System.out.println(v.count);
  }
}

2、禁止指令重排序

指令重排序也是和CPU有关系,加了volatile之后,每次写都会背线程看到。CPU原来执行指令时,是按照一步一步顺序来执行的,但是CPU为了提高效率它会把指令并发来执行,第一个指令执行到一半的时候第二条指令就可能已经开始执行了,这叫流水线式的执行;为了充分的利用CPU,就要求编译器把编译完的源码指令,可能会进行一个指令重新排序;这种架构通过实际验证,很大效率上提高了CPU的使用效率。

下面从一个面试题来讨论一下指令重排序:

面试官:你听过单例模式吗?

你:当然听过,不然没法聊了。

package com.designmodal.design.juc01;

import java.util.concurrent.TimeUnit;

/**
 * @author D-L
 * @Classname T002_volatile
 * @Version 1.0
 * @Description volatile 指令重排序
 * @Date 2020/7/20 00:48
 */
public class T002_volatile {
  //创建私有的 T002_volatile 有人会问这里的volatile要不要使用,这里的答案是肯定的
  private static /**volatile*/ volatile T002_volatile INSTANCE;

  public T002_volatile() {}

  public T002_volatile getInstance(){
    //模拟业务代码 这里为了synchronized更加细粒度,所以使用了双重检查
   if(INSTANCE == null){
     synchronized (this){
       //双重检查
       if(INSTANCE == null){
         //避免线程之间的干扰 在这里睡一秒
         try {
           TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
         //创建实例对象
         INSTANCE = new T002_volatile();
       }
     }
   }
   return INSTANCE;
  }

  /**
   * 创建100个线程 调用getInstance() 打印hashcode值
   * @param args
   */
  public static void main(String[] args) {
    T002_volatile t001_volatile = new T002_volatile();
    for (int i = 0; i < 100; i++) {
      new Thread(() ->{
        T002_volatile instance = t001_volatile.getInstance();
        System.out.println(instance.hashCode());
      }).start();
    }

  }
}

在上述的代码中:INSTANCE = new T002_volatile(); 经过编译后的指令是分三步的

1、给指令申请内存

2、给成员变量初始化

3、把这块对象的内容赋给INSTANCE

在第二步这里既然已经有默认值了,第二个线程来检查,发现已经有值了根本就不会进入锁住的那份代码;加了volatile就不会出现指令重排序了,所以在这个时候一定要保证初始化完成之后才会赋值给这个变量,这就是volatile存在的意义。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • Java实现经典游戏复杂迷宫

    这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
  • C# WinForm多线程解决界面卡死问题的完美解决方案,使用BeginInvoke

    问题描述:当我们的界面需要在程序运行中不断更新数据时,当一个textbox的数据需要变化时,为了让程序执行中不出现界面卡死的现像,最好的方法就是多线程来解决一个主线程来创建界...2020-06-24
  • jquery实现加载更多"转圈圈"效果(示例代码)

    这篇文章主要介绍了jquery实现加载更多"转圈圈"效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-11-10
  • java 运行报错has been compiled by a more recent version of the Java Runtime

    java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01
  • 在java中获取List集合中最大的日期时间操作

    这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 教你怎么用Java获取国家法定节假日

    这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • C#停止线程的方法

    这篇文章主要介绍了C#停止线程的方法,实例分析了C#正确停止线程的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C#开启线程的四种方式示例详解

    今天小编就为大家分享一篇关于C#开启线程的四种方式示例详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...2020-06-25
  • 浅谈Java与C#的一些细微差别

    说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • C# 线程相关知识总结

    这篇文章主要介绍了C# 线程相关知识,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-11-03
  • c# 多线程处理多个数据的方法

    这篇文章主要介绍了c# 多线程处理多个数据的方法,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...2021-03-31
  • C#实现跨线程操作控件方法

    这篇文章主要介绍了C#实现跨线程操作控件方法,主要采用异步访问方式实现,需要的朋友可以参考下...2020-06-25
  • java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • Java生成随机姓名、性别和年龄的实现示例

    这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
  • java 画pdf用itext调整表格宽度、自定义各个列宽的方法

    这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31
  • C#基于委托实现多线程之间操作的方法

    这篇文章主要介绍了C#基于委托实现多线程之间操作的方法,实例分析了C#的委托机制与多线程交互操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • java正则表达式判断前端参数修改表中另一个字段的值

    这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07