文章
问答
冒泡
从Java内存模型的角度看Volatile

Volatile


在多线程编程中,我们常常用到Volatile来维护变量的可见性,他是一个比较弱的同步机制。相较于sychronized关键字,在访问共享变量的时候,volatile不会进行加锁,因此他也不会造成线程阻塞,所以他更加轻量!


深入点看的话,当我们将变量声明为volatile类型的时候,编译与运行的时候,我们都会注意到这个变量是共享的,因此不会对将对该变量的操作和其他内存块一起 重排序,volatile不会被缓存到寄存器或者其他处理器不可见的地方,因此在读取volatile变量时,总会返回最新写入的值。


上文提到了一个词,重排序 ,何为重排序?

重排序即编译器和处理器对代码指令的动态优化 会打乱原本的java代码的执行顺序。而在程序从源代码到机器指令执行的过程中会发生多次的重排序。


既然重排序这是编译器和处理器做的事情,那么我们是怎么来避免了重排序呢?那便是我们下面要说的java内存模型



JAVA内存模型


我们接着说回volatile,我们知道volatile保证了多线程下对变量的可见性。而普通变量做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成。例如,线程A修改了普通变量的值,那么他需要将这个变量向主内存进行回写,另外一个线程B在A回写完成之后再从主内存中进行读取,新变量才会对线程B可见。


主内存与工作内存

java内存模型规定了所有的变量都存储在主内存中,每条线程都有自己的工作内存。线程的工作内存中保存了该线程使用到的变量在主内存中的拷贝(所谓拷贝并不是将整个对象拷贝,而是拷贝的对象的引用),线程对该对象的所有操作都是在工作内存中进行,而不是直接去读写主内存,他们之间的关系如下图所示



注意 这里讲的主内存,工作内存与java内存区域中的java堆,栈,方法区等不是同一个层次的内存划分,他们直接没有任何的关系


由上可知如果并发线程中也经过这样的内存流程,那么便会造成线程不安全,而java内存模型中针对volatile修饰的变量,是只要有一条线程修改了这个变量的值,会立即通知到其他使用到这个变量的线程,让其他线程立即可知,这便是volatile的可见性。


volatile是线程安全的吗?

从以上的论据可以得到volatile在各个线程中是一致的,所以基于volatile变量运算在并发下是线程安全的。但是! 我们不能说基于volatile的运算在并发下是线程安全的!

看以下代码


	public class VolatileTest {

    public static volatile int race = 0;

    public static void addRace(){
        race ++;
    }

    public static void main(String[] args){
        Thread[] threads = new Thread[20];

        for (int i=0;i < 20;i++){
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0;i<10000;i++){
                        addRace();
                    }
                }
            });
            threads[i].start();
        }

        while (Thread.activeCount() > 1){
            Thread.yield();

            System.out.println(race);
        }
    }
}

理论上,如果并发正确的话,最终打印出来的值应该是200000,但是每次得到的结果其实都是小于200000的某一个值,因为在我们进行++ 运算的时候其实包含了多个操作,取出race变量放到栈顶,然后再进行赋值,再返回给使用线程的操作,因为保证了栈顶元素的可见性,其他线程这个时候也可更改此变量,而当上一个线程执行完成后,他会刷到主内存中,这样,便会造成会有不正确的数据。


所以可得结论,volatile只能保证可见性,并不能保证数据的原子性,如果需要保证一致性,我们需要借助synchronized或者JUC中的原子类


public static volatile int race = 0;

public static synchronized void addRace(){
    race ++;
}


关于作者

silen
三流码农
获得点赞
文章被阅读