你应该知道的 volatile 关键字(上)

简介: 不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。首先来看看为什么会出现这个关键字。

内存可见性


由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。


线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。


这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存


如下图所示:



所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。


显然这肯定是会出问题的,因此 volatile 的作用出现了:


当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。


volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中


内存可见性的应用


当我们需要在两个线程间依据主内存通信时,通信的那个变量就必须的用 volatile 来修饰:


public class Volatile implements Runnable{
    private static volatile boolean flag = true ;
    @Override
    public void run() {
        while (flag){
            System.out.println(Thread.currentThread().getName() + "正在运行。。。");
        }
        System.out.println(Thread.currentThread().getName() +"执行完毕");
    }
    public static void main(String[] args) throws InterruptedException {
        Volatile aVolatile = new Volatile();
        new Thread(aVolatile,"thread A").start();
        System.out.println("main 线程正在运行") ;
        TimeUnit.MILLISECONDS.sleep(100) ;
        aVolatile.stopThread();
    }
    private void stopThread(){
        flag = false ;
    }
}


主线程在修改了标志位使得线程 A 立即停止,如果没有用 volatile 修饰,就有可能出现延迟。


但这里有个误区,这样的使用方式容易给人的感觉是:


volatile 修饰的变量进行并发操作是线程安全的。


这里要重点强调,volatile不能保证线程安全性!


如下程序:


public class VolatileInc implements Runnable{
    private static volatile int count = 0 ; //使用 volatile 修饰基本数据内存不能保证原子性
    //private static AtomicInteger count = new AtomicInteger() ;
    @Override
    public void run() {
        for (int i=0;i<10000 ;i++){
            count ++ ;
            //count.incrementAndGet() ;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        VolatileInc volatileInc = new VolatileInc() ;
        Thread t1 = new Thread(volatileInc,"t1") ;
        Thread t2 = new Thread(volatileInc,"t2") ;
        t1.start();
        //t1.join();
        t2.start();
        //t2.join();
        for (int i=0;i<10000 ;i++){
            count ++ ;
            //count.incrementAndGet();
        }
        System.out.println("最终Count="+count);
    }
}


当我们三个线程(t1,t2,main)同时对一个 int 进行累加时会发现最终的值都会小于 30000。


这是因为虽然 volatile 保证了内存可见性,每个线程拿到的值都是最新值,但 count ++ 这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。


  • 所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。


  • 也可以使用 synchronize 或者是锁的方式来保证原子性。


  • 还可以用 Atomic 包中 AtomicInteger 来替换 int,它利用了 CAS 算法来保证了原子性。


相关文章
|
6月前
|
缓存 编译器
volatile关键字
volatile关键字
|
6月前
|
缓存 编译器 C语言
一起来探讨volatile关键字
在C语言中,volatile是一个关键字,用于告诉编译器不要对被声明为volatile的变量做优化,以确保每次对该变量的读写都直接操作内存。
|
缓存 安全 Java
【volatile关键字】
【volatile关键字】
|
存储 缓存 Java
volatile 关键字说明
volatile 关键字说明
51 0
|
3月前
|
存储 Java 编译器
|
3月前
|
缓存 Java 编译器
关键字: volatile详解
综上所述,`volatile`关键字是Java中实现轻量级同步的一个重要手段,主要用于确保变量的跨线程可见性,但并不保证操作的原子性。在多线程编程的过程中,合理地选择和使用 `volatile`关键字,对于提高程序的正确性和性能都至关重要。
40 0
|
SQL 缓存 Java
Volatile关键字介绍
Volatile关键字介绍
Volatile关键字介绍
|
存储 缓存 Java
volatile关键字再理解
volatile关键字再理解
volatile关键字再理解
|
SQL 缓存 安全
深入理解volatile关键字
深入理解volatile关键字
194 0
深入理解volatile关键字
|
缓存 前端开发 Java
volatile关键字有什么用?
volatile关键字有什么用?
volatile关键字有什么用?