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

volatile关键字解读

程序员文章站 2022-07-13 14:50:39
...

volatile英文单词是不稳定的意思,其实在Java中也是词如其意。 在很多面试的时候,volatile问得比较多,也是比较重要。

介绍

volatile是Jvm虚拟机提供的轻量级同步机制
有如下特性:

保证可见性

说起volatile的可见性,必须要提到的就是JMM(Java Memory Model)Java内存模型,它是一个抽象的概念,只是一个模型,描述一组规范或者说规则。通过这些规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM有以下规定

  • 线程解锁前,必须把共享变量的值刷写到主内存
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 加锁和解锁是一把锁

我们知道JVM运行程序的实体是线程,每个线程JVM都会为其创建工作内存(栈),
工作内存是每个线程的私有区域,其他线程处理主内存之外,不能够访问其他线程的工作内存
Java中的所有变量都存储在主内存中,主内存是所有线程共享的区域,但是对变量的操作(读取赋值等)必须在工作内存中进行,不能够直接在主内存中直接操作变量

线程是把主内存中的变量copy一份到各自的工作内存中,在进行操作。线程之间的操作也是由主内存完成。

但是其实还是有个问题,就是如果线程A修改了共享变量X,但是未写到主内存中,线程B又对X进行操作,A对X的操作对B是不可见的。工作内存和主内存之间的延迟会导致可见性的问题。
volatile关键字解读
代码demo

class MyData{
    volatile int number  = 0;
    public void updatenumber20(){
        this.number = 20;
    }
    void numberplus2(){
        number++;
    }
}

private static void seeOkVolatile() {
    MyData myData = new MyData();
    new Thread(() ->{
        System.out.println(Thread.currentThread().getName()+"\t come in");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myData.updatenumber20();
        System.out.println(Thread.currentThread().getName()+"\t update number value :"+myData.number);
    },"AAA").start();

    //第二个线程为主线程-->main
    while (myData.number ==0){
        //如果number不加volatile那么会一直等待,知道number不等于0
    }
    System.out.println(Thread.currentThread().getName()+"\t mission is  over ");
}
不保证原子性

说到原子性,大家应该不会陌生,在学习数据库的时候也有接触。
原子性就是不可分割,完整性,某个线程在进行某个操作的时候,要么成功,要么失败,就比如银行转账一样。
但是volatile并不会保证原子性,如果没有加锁,那么所有的线程都会争抢资源,如果一个线程操作完成后,可能没有把更新结果返回给主内存,其它的线程就是对其进行操作
解决方法:

  • 使用juc下的AtomicInteger
  • 家sync
class MyData{
    volatile int number  = 0;
    public void updatenumber20(){
        this.number = 20;
    }
    void numberplus2(){
        number++;
    }
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtomic(){
        atomicInteger.getAndIncrement();
    }
}

private static void atom() {
    MyData myData = new MyData();
    for (int i = 0; i <2 ; i++) {
        new Thread(() ->{
            for (int j = 0; j <10000 ; j++) {
              //  System.out.println("Thread.activeCount(): "+Thread.activeCount());

                myData.numberplus2();
               myData.addMyAtomic();
            }
        },String.valueOf(i)).start();
    }
    //需要等待上面的29个线程全部计算完成后,再用main线程取得最终的结果值
    while (Thread.activeCount() >2){
        Thread.yield();
    }

    System.out.println(Thread.currentThread().getName()+"\t int type finally number value "+myData.number);
   System.out.println(Thread.currentThread().getName()+"\t atomicInteger type finally number value "+myData.atomicInteger);
}

//main  int type finally number value 17015
//main  atomicInteger type finally number value 20000
有序性 禁止指令重排

volatile实现有序性禁止指令重排,从而避免在多线程环境下程序出现乱序执行的情况

在之前先说一下内存屏障(Memorry Barrier) 又被称为内存栅栏,它是一个CPU的指令,主要作用如下

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性(volatile的可见性则利用此特性)
  • 强制刷写各种CPU缓存数据,因此在任何CPU上的线程都可以读到数据的最新版本

由于编译器和处理器在执行指令的时候都对指令重排优化,如果在指令间插入一条Memory Brrrier,则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重拍序,也就是说通过插入Memory Barrier指令禁止内存屏障前后的指令重排优化。

volatile关键字解读
volatile关键字解读
volatile关键字解读volatile关键字解读
volatile关键字解读

volatile在单例模式中很常见

相关标签: JUC