了解volatile

当我们聊到volatile的时候,只需要围绕它的三个特性来了解即可

1.保证可见性

2.不保证原子性

3.禁止指令重排

那么我们则需要细化这三个特性的内容

1.volatile是如何保证可见性的呢?
在java中还有一个约定的概念JMM(java内存模型)我们在建立线程的同时,线程将会分为主内存+工作内存

而我们在进行一些共享内存变量(比如静态变量)的一些操作的时候

JMM则有一些同步约定:1.线程解锁前,共享变量必须刷新到主内存中 2.线程加锁前,必须将主内存的共享变量读取到工作内存中 3.加锁和解锁必须是同意把锁

那么这个设定,对于多个线程同时操作共享变量时,则会遇到线程A未运行完毕,线程B已经修改了共享变量的值,而引发一系列问题:这就是共享变量不可见造成的。

所以我们就需要用到volatile来保证共享变量的可见性。通过volatile的修饰,线程A,B能够在内存中看到被修饰的共享变量,而在修改的时候,及时通知运行时的线程,从而避免问题。

public class volatileTest {
    //不加 volatile 程序就会死循环
    //加volatile 保证了可见性
    private volatile static int num = 0;
    public static void main(String[] args) {//main线程
        new Thread(()->{//线程1
            while(num==0){

            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

2.为什么volatile不保证原子性

在java中我们可以随意用一个例子来进行分析,比如volatile static int num=0; num++;

我们通过javap对于编译的字节码文件进行查看的时候可以发现,编译器实际上把a++分成了多步,首先是静态变量的出栈,然后计算,最后入栈。像这种多步操作往往就会存在线程安全的隐患。从而是非原子性操作。

那我们应该怎样保证变量的原子性呢?

1.加共享锁:lock/syncronized

2.使用原子类

原子类为什么能够保证变量原子性 这里我们需要在CAS中进行详解。

不过我们可以先看下这些原子类的源码

以AtomicInteger为例,实际上在进行计算的时候,AtomicInteger是使用的Unsafe类进行的计算

而在Unsafe类中,我们将会看到大量的native修饰的方法,这些方法则是以c编写的方法,也就是说这些对于变量的计算 都是通过直接在内存中修改变量的值,从而保证原子性的操作。

3.禁止指令重排

什么是指令重排?

在我们的操作系统中,对于源码可能会有以下操作 1.编译器优化重排 2.指令并行重排 3.内存系统重排

这样的话 将会导致一个问题,我们所编写的程序指令,可能不会按照我们想要的顺序来执行

那么这个时候,我们就要用到volatile来修饰变量,来禁止指令重排

可以看到,这个时候在内存中的指令可以分为普通指令,和volatile指令,当我们对于变量添加了volatile描述之后
系统会对于volatile指令的前后方添加内存屏障,从而避免指令重排。

4.当我们了解volatile之后,它用的最多的地方是哪里呢:单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值