美文网首页
Volatile关键字与三大特征的关系

Volatile关键字与三大特征的关系

作者: CoderInsight | 来源:发表于2023-10-13 09:36 被阅读0次

在Java虚拟机中通过Volatile关键字提供了轻量级的同步机制:保证可见性、不保证原子性、可禁止指令重排

  1. 当一个变量被Volatitle修饰了之后,JMM会把该线程的本地内存的共享变量刷新到主内存;同时会将本地内存置为无效,线程接下来会从主内存中读取共享变量。 ==> 也就说使用主内存保证了可见性。
  2. 对于指令重排我们知道是在不影响单线程程序执行结果的前提下JMM优化的一种结果。那么可以使用Volatitle 禁止指令重排,从而实现有序性。
  3. 而有序性的详细实现是基于 Volatilehappens-befor 关系:其实就是一个传递性的连接关系,A > B, B > C => A > C,这种连接关系就是 happens-befor 关系。(补充数学原理:偏序关系 -> 偏序关系是集合中元素之间的一种关系,它满足反身性(对于任意元素a,a和自己存在关系)、反对称性(如果a和b存在关系,且b和a存在关系,则a和b必须相等)和传递性(如果a和b存在关系,b和c存在关系,则a和c也必须存在关系)。

1),应用场景:状态变量标记

对于某个状态变量被多个线程共享的时候,其他的线程需要能及时感知到这个变量的变化情况,则可以通过volatile关键字修饰变量,以此可以保证修改对其他线程立刻可见(实现的原理:将变量直接放在主内存中,并且不会被线程缓存)。另外这种方式对于读写操作都是无锁的,它无法完全替代 synchronized、Lock,因为它并没有提供原子性,但是基于多线程共享层面来讲,由于不存在加锁和释放锁的动作,所以其成本是低于synchronized、Lock 的。

2),应用场景:单例模式

典型双重检查锁定(DCL),如下代码(创建对象)分析:instance= new Singleton(); 这段代码其实是分为三步执行:

  1. instance 分配内存空间
  2. 初始化 instance
  3. instance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 instance 不为空,因此返回 instance,但此时 instance 还未被初始化。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行

class Singleton{
    // 通过volatile禁止指令重排
    private volatile static Singleton instance = null;
    private Singleton() {
    }
    // 通过静态方法将创建对象的方法提供给外部调用
    public static Singleton getInstance() {
        //第一次加锁是为了性能考虑,因为线程安全发生在对象初始化,如果第一次不判断相当于全局控制,造成浪费。
        if(instance == null) {
            synchronized (Singleton.class) {
                // 双检锁:即通过两次判断来避免两个线程同时创建对象,这样当第一个创建对象完成,第二个会直接结束,不再创建
                if(instance==null)
                {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

3),Volatile为啥不能保证原子性?

因为Volatile关键字是通过将变量保存到主内存中实现的可见性,即便多个线程对于该变量的值是可见的,但是因为多线程竞争阻塞(比如计算i++),可能会出现线程A、B同时读到了变量的值0,但是A阻塞了,B获取将值加1,改变了变量的值,但是对于线程A来说,也早就读取到了变量的值为0此时它也会加1,那么并不会出现结果2,而是由1到1,即最终仍然是1的情况。

public class DemoTest {  
// 如果是使用volatile修改的i++,是无法保证原子性的,每次输出的结果都是不一致的
//    static volatile int  i = 0;  
    // 使用并发安全的集合,此时计算结果是正确的100000
    static AtomicInteger atomicInteger = new AtomicInteger();  
    public static void main(String[] args) throws InterruptedException {  
        for (int j = 0; j < 2; j++) {  
            new Thread(  
                    () -> {  
                        for (int k = 0; k < 50000; k++) {  
//                            i++;  
                            atomicInteger.incrementAndGet();  
                        }  
                        System.out.println("线程执行完毕");  
                    }  
            ).start();  
        }  
        // 当前等待是必然要有的,不然计算结果始终为0
        Thread.sleep(1000);  
//        System.out.println("最终计算结果:" + i);  
        System.out.println("最终计算结果:" + atomicInteger.get());  
    }  
}

相关文章

  • Java Volatile transient 关键字

    Java Volatile transient 关键字 java关键字volatile Volatile修饰的成员...

  • 1.4 volatile 关键字

    volatile 关键字 volatile关键字是与Java的内存模型有关的,因此需要先了解一下与内存模型相关的概...

  • Volatile总结

    volatile被视作是轻量级的sychronized。与sychronied关键字比较,volatile只能保证...

  • 2.3volatile关键字

    关键字volatile的主要作用是使变量在多个线程间可见。 关键字volatile与死循环 如果不是在多继承的情况...

  • DCL双重检查锁定与延迟初始化

    infoq的这篇文章讲的不错双重检查锁定与延迟初始化 这篇讲volatile关键字也不错volatile关键字 1...

  • volatile

    volatile关键字private volatile int count; volatile关键字主要有三方面作...

  • synchronized 关键字和 volatile 关键

    synchronized关键字和volatile关键字比较volatile关键字是线程同步的轻量级实现,所以vol...

  • java线程同步之volatile

    volatile的概念: volatile与synchronized关键字是多线程并发编程中非常重要的知识点,通常...

  • 【转载】Java 理论与实践: 正确使用 Volatile 变量

    转载自 IBM Java 理论与实践: 正确使用 Volatile 变量 你真的了解volatile关键字吗? v...

  • java-volatile解析

    volatile关键字,熟悉的朋友应该能总结到该关键字的特征如下: 1.保证可见性、但不保证原子性 2.禁止指令重...

网友评论

      本文标题:Volatile关键字与三大特征的关系

      本文链接:https://www.haomeiwen.com/subject/drxabdtx.html