美文网首页
java中的volatile

java中的volatile

作者: CoderZzbJohn | 来源:发表于2018-12-24 22:49 被阅读0次

https://www.cnblogs.com/chengxiao/p/6528109.html

public class TestVolatile {
    boolean status = false;

    /**
     * 状态切换为true
     */
    public void changeStatus(){
        status = true;
    }

    /**
     * 若状态为true,则running。
     */
    public void run(){
        if(status){
            System.out.println("running....");
        }
    }
}

1.JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。这三者之间的交互关系如下

image.png

大概了解了JMM的简单定义后,问题就很容易理解了,对于普通的共享变量来讲,比如我们上文中的status,线程A将其修改为true这个动作发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B缓存了status的初始值false,此时可能没有观测到status的值被修改了,所以就导致了上述的问题。那么这种共享变量在多线程模型中的不可见性如何解决呢?比较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,有点炮打蚊子的意思。比较合理的方式其实就是volatile

volatile具备两种特性,第一就是保证共享变量对所有线程的可见性。将一个共享变量声明为volatile后,会有以下效应:

1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去;

2.这个写会操作会导致其他线程中的缓存无效。

上面的例子只需将status声明为volatile,即可保证在线程A将其修改为true时,线程B可以立刻得知

  1. JMM内存模型。主内存,本地内存。共享变量存储在主内存中,当各个线程线程有自己的本地内存。使用变量时,先从共享内存中获取变量,然后存储在本地内存中,修改了值之后,刷回主内存中。

4.当一个变量用volatile修饰时,某个线程获取到了变量并修改了值之后,其他线程中本地内存的变量值会失效。需要重新从主内存中获取。

/*
https://www.cnblogs.com/chengxiao/p/6528109.html
 */
public class Test {

    //static volatile int sum = 0;
    static AtomicInteger sum=new AtomicInteger(0);

    public static void main(String[] args) {

        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        //num++不是个原子性的操作,而是个复合操作   
                        //   1.读取  get
                        //    2.  加一赋值  set
                        //   如果在get了之后的时候,线程切换了。另一个线程修改了这个值,那么原来线程读到的值就是旧的数据。所以需要加上CAS,CAS操作每次在读出来,写回去之前,先判断读出来的值是否等于expect。如果不相等则重新读。
需要volatile+cas配合。voilatile保证了重新get的时候,不会从线程缓存中读,而是直接去主内存中读。cas保证了读出来之后,线程被切换了修改了值,能重新做读这个动作。
                        //sum++;
                        sum.addAndGet(1);
                    }
                    countDownLatch.countDown();
                }
            }.start();

        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sum.get());

    }
}

volatile还有一个特性:禁止指令重排序优化。

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。但是重排序也需要遵守一定规则:

1.重排序操作不会对存在数据依赖关系的操作进行重排序。

比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。

2.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

重新排序问题: volatile变量前面的命令可以随意重排,但是不能拍到volatile之后去。同理volatile之后的命令,不能排到volatile之前。
所有对volatile变量的写操作之前的针对其他变量的读写操作,经过编译器、cpu优化后,都不会被重排到对voltile变量的写操作之后。
所有对volatile变量的读操作之后的针对其他变量的读写操作,经过编译器、cpu优化后,都不会被重排到对voltile变量的读操作之前。

volatile是一种轻量级的同步机制,它主要有两个特性:一是保证共享变量对所有线程的可见性;二是禁止指令重排序优化。同时需要注意的是,volatile对于单个的共享变量的读/写具有原子性,但是像num++这种复合操作,volatile无法保证其原子性,当然文中也提出了解决方案,就是使用并发包中的原子操作类,通过循环CAS地方式来保证num++操作的原子性。关于原子操作类,会在后续的文章进行介绍。

相关文章

网友评论

      本文标题:java中的volatile

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