欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-01 18:53:44
首先volatile有两大功能:保证线程可见性禁止指令重排序1、保证线程可见性首先我们来看这样一个程序,其中不加volatile关键字运行的结果截然不同,加上volatile程序能够正常结束,不加则程...

首先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变量改变其他线程很快就会知道,这就是线程的可见性;

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

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

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

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存在的意义。

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