首页 > 其他分享 >volatile关键字

volatile关键字

时间:2024-07-19 12:40:20浏览次数:8  
标签:SingleInstanceTest 变量 关键字 INSTANCE volatile new public

volatile 可以保证可见性,但不保证原子性:

  • 当写一个 volatile 变量时,JMM 会把该线程在本地内存中的变量强制刷新到主内存中去;
  • 这个写操作会导致其他线程中的 volatile 变量缓存无效。

volatile 会禁止指令重排

重排序需要遵守的规则:

  • 重排序不会对存在数据依赖关系的操作进行重排序。比如:a=1;b=a; 这个指令序列,因为第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
  • 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。比如:a=1;b=2;c=a+b 这三个操作,第一步 (a=1) 和第二步 (b=2) 由于不存在数据依赖关系,所以可能会发生重排序,但是 c=a+b 这个操作是不会被重排序的,因为需要保证最终的结果一定是 c=a+b=3。

当使用 volatile 关键字来修饰一个变量时,Java 内存模型会插入内存屏障(一个处理器指令,可以对 CPU 或编译器重排序做出约束)来确保以下两点:

  • 写屏障(Write Barrier):当一个 volatile 变量被写入时,写屏障确保在该屏障之前的所有变量的写入操作都提交到主内存。
  • 读屏障(Read Barrier):当读取一个 volatile 变量时,读屏障确保在该屏障之后的所有读操作都从主内存中读取。

换句话说:

  • 当程序执行到 volatile 变量的读操作或者写操作时,在其前面操作的更改肯定已经全部进行,且结果对后面的操作可见;在其后面的操作肯定还没有进行;
  • 在进行指令优化时,不能将 volatile 变量的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

也就是说,执行到 volatile 变量时,其前面的所有语句都必须执行完,后面所有得语句都未执行。且前面语句的结果对 volatile 变量及其后面语句可见。

volatile 不适用的场景

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        Test test = new Test();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++){
                        test.increase();
                    }
                }
            }).start();
        }
        // 保证前面的线程都执行完
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        // inc output:9889
        System.out.println("inc output:" + test.inc);
    }
}
  • IDEA用不加断点的 debug 模式运行。(直接运行,IDEA 还会开启一个 Monitor Ctrl-Break 线程)

  • inc++不是一个原子性操作,由读取、加、赋值 3 步组成,所以结果并不能达到 10000

解决办法:

  1. 方法前加上 synchronized
public synchronized void increase() {
    inc++;
}
  1. 采用 Lock,通过重入锁 ReentrantLock inc++ 加锁
Lock lock = new ReentrantLock();

public void increase() {
    lock.lock();
    inc++;
    lock.unlock();
}
  1. 采用原子类 AtomicInteger
public AtomicInteger inc = new AtomicInteger();

public void increase() {
    inc.getAndIncrement();
}

volatile 实现双重检测锁的单例模式

public class SingleInstanceTest {
    private SingleInstanceTest() {
    }

    // 使用 volatile 关键字是为了防止 INSTANCE = new SingleInstanceTest(); 这一步被指令重排序
    private volatile static SingleInstanceTest INSTANCE;

    public static SingleInstanceTest GetInstance() {
        if (INSTANCE == null) {
            synchronized (SingleInstanceTest.class) {
                if (INSTANCE == null) {
                    // new SingleInstanceTest()有三个子步骤
                    // 步骤 1:为 SingleInstanceTest 对象分配足够的内存空间,伪代码 memory = allocate()。
                    // 步骤 2:调用 SingleInstanceTest 的构造方法,初始化对象的成员变量,伪代码 ctorInstanc(memory)。
                    // 步骤 3:将内存地址赋值给 INSTANCE 变量,使其指向新创建的对象,伪代码 INSTANCE = memory。
                    INSTANCE = new SingleInstanceTest();
                }
            }
        }
        return INSTANCE;
    }
}

标签:SingleInstanceTest,变量,关键字,INSTANCE,volatile,new,public
From: https://www.cnblogs.com/sprinining/p/18311264

相关文章

  • MySQL中的using关键字
    先创建两张表CREATETABLEemployees(employee_idINTAUTO_INCREMENTPRIMARYKEY,nameVARCHAR(50),positionVARCHAR(50),salaryDECIMAL(10,2));INSERTINTOemployees(employee_id,name,position,salary)VALUES(1,'JohnDoe'......
  • java基础知识(3)—关键字
    在Java编程的广阔领域中,关键字宛如一把把精确的工具,赋予开发者准确表达意图和实现复杂逻辑的能力。访问控制关键字:private:确保变量、方法或内部类仅在所属的类内部可访问,为数据提供了最高级别的隐私保护。protected:在继承关系中,允许子类和同一包中的类访问特定的成员。pu......
  • 【final 关键字的理解】
    final关键字final关键字主要作用为防止数据被修改,保证代码安全。可以用于修饰:类、类中方法、变量、参数;修饰类相关代码代码解读finalclassPerson{}//错误,不可以继承被final修饰的类classStudentextendsPerson{}概念当使用final关键字修饰类的时候,表......
  • C#中implicit 关键字的使用:隐式转换操作符
    在C#中,implicit 关键字用于定义隐式转换操作符。隐式转换操作符允许自动将一种数据类型转换为另一种类型,而无需显式地调用转换方法或进行类型转换。下面将详细介绍 implicit 关键字的定义和使用。1.隐式转换操作符定义隐式转换操作符可以定义在一个类或结构体中,使得该......
  • auto,static,const,extern,volatile,register
    auto关键字用于声明变量的生存期为自动,auto修饰的是自动类型的变量,对于局部变量默认就是自动类型的变量,如果没有赋初值它的值就是随机值。static 修饰的变量或者函数有如下特点:static修饰的局部变量,可以延长变量的生命周期(不会被多次初始化)static修饰的全局变量或者函数只......
  • 【Azure Developer】C#/.NET 静态函数中this关键字的作用
    问题描述在查看.NET代码的时候,发现一个静态方法,第一个参数使用this关键字,它在这里是什么作用呢?publicstaticXElementAquireElement(thisXContainercontainer,stringname,booladdFirst=false){... 问题解答通过查看微软的官方博文介......
  • C#基础:partial关键字和类的继承
    代码示例publicpartialclassForm1:Form{publicForm1(){InitializeComponent();}//Button的Click点击事件(自动添加)privatevoidshowMessage(objectsender,EventArgse){MessageBox.Show("HelloWorld!");}......
  • C语言中关键字static
    前言    在C语言中,static 是一个关键字,它可以用在不同的上下文中,赋予变量或函数不同的意义。static 关键字主要用于控制变量的存储期和可见性,以及函数的链接性。下面是 static 关键字的主要作用原理与用途:局部静态变量    当 static 修饰局部变量时(即......
  • C++ static关键字
    在C++中,static关键字有多种用途,主要用于控制变量和函数的存储期和链接性。下面详细介绍static关键字在不同上下文中的用法,并提供相应的代码示例。1.静态局部变量静态局部变量在函数中定义,但它们的生命周期贯穿程序的整个运行周期,而不仅仅是函数的执行周期。静态局部变量......
  • auto关键字
    作用:能自动推断出是什么数据类型代码示例:autoname()//能判断出返回值时字符串类returnstd::string("hhh");弊端:对变量类型不明确,可能会破坏依赖特定类型的代码比如字符串类的.strlen()什么时候用:当类型过长时intmain(){std::vector<std::string>strings;strin......