动力节点旗下在线教育品牌  |  咨询热线:400-8080-105 学Java全栈,上蛙课网
首页 > 文章

实例分析关键字volatile作用

08-12 16:50 253浏览
举报 T字号
  • 大字
  • 中字
  • 小字

volatile是一个特征修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。由于volatile关键字是与Java的内存模型有关的,因此想要全面分析关键字volatile的作用就要从内存模型出发。

我们先来看下何谓内存模型:程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

volatile关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符出现,用来修饰变量,但是这里不包括局部变量哦。用实际Demo说话:

/**

 *  @Author 捡田螺的小男孩

 *  @Date 2020/08/02

 *  @Desc volatile的可见性探索

 */

public class VolatileTest  {

 

    public static void main(String[] args) throws InterruptedException {

        Task task = new Task();

 

        Thread t1 = new Thread(task, "线程t1");

        Thread t2 = new Thread(new Runnable() {

            @Override

            public void run() {

                try {

                    Thread.sleep(1000);

                    System.out.println("开始通知线程停止");

                    task.stop = true; //修改stop变量值。

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

 

            }

        }, "线程t2");

        t1.start();  //开启线程t1

        t2.start();  //开启线程t2

        Thread.sleep(1000);

    }

}

 

class Task implements Runnable {

    boolean stop = false;

    int i = 0;

 

    @Override

    public void run() {

        long s = System.currentTimeMillis();

        while (!stop) {

            i++;

        }

        System.out.println("线程退出" + (System.currentTimeMillis() - s));

    }

}

运行结果:

开始通知线程停止

一、volatile关键字保证变量对所有线程的可见性,

我们可以发现线程t2,虽然把stop设置为true了,但是线程t1对t2的stop变量视而不可见,因此,它一直在死循环running中。如果给变量stop加上volatile修饰,线程t1是可以停下来的,运行结果如下:

volatile boolean stop = false;

根据上面的分析不难发现变量stop,加了vlatile修饰之后,线程t1对stop就可见了。一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2.禁止进行指令重排序。

 

二、禁止指令重排

  先看一段代码,假如线程1先执行,线程2后执行:

//线程1

boolean stop = false;

while(!stop){

    doSomething();

}

//线程2

stop = true;

   这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。

  但是用volatile修饰之后就变得不一样了:

  第一:使用volatile关键字会强制将修改的值立即写入主存;

  第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

  第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么线程1读取到的就是最新的正确的值。

  • 不保证原子性

所谓原子性是指在一次的操作或者多次操作中,要么所有的操作都得到了执行并且不会受到任何因素的干扰或中断,要么所有的操作都不执行。

volatile关键字不具备保证原子性的语义。如果想使得某些代码具备原子性,需要使用关键字synchronized,或者JUC中的lock。如果想使int等类型的自增具有原子性,可以使用JUC包下的原子封装类java.util.concurrent.atomic.*。

public class Test {

    public volatile int race = 0;

     

    public void increase() {

        race++;

    }

     

    public static void main(String[] args) {

        final Test test = new Test();

        for(int i=0;i<10;i++){

            new Thread(){

                public void run() {

                    for(int j=0;j<100;j++)

                        test.increase();

                };

            }.start();

        }

        

        //等待所有累加线程结束

        while(Thread.activeCount()>1)  

            Thread.yield();

        System.out.println(test.race);

    }

}

 

  其实总的来说,关键字volatile的作用还是围绕着并发程序的三个特性展开的。我们在学习Java的过程中还会遇到许多这种知识串联在一起的情况,需要我们温故知新,才能学得更好。

0人推荐
共同学习,写下你的评论
0条评论
夜未央
程序员夜未央

6篇文章贡献26223字

作者相关文章更多>

推荐相关文章更多>

Java数据结构

HelloWorld10-31 08:24

浅谈MySQL中SQL优化的常用方法

军哥08-12 23:29

五分钟读懂UML类图

江湖人称小李白12-10 10:41

MyBatis开发框架的四大核心

IT逐梦者08-17 21:43

一次搞定continue,break和return

HelloWorld11-06 11:19

发评论

举报

0/150

取消