美文网首页
【Java】volatile简述

【Java】volatile简述

作者: littlefogcat | 来源:发表于2021-03-24 13:39 被阅读0次

    一、volatile解决了什么问题?

    对于以下代码:

        private static boolean flag = true;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                while (flag) ;
                System.out.println("thread 1 out");
            });
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(1); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                flag = false;
                System.out.println("thread 2 out");
            });
            t1.start();
            t2.start();
        }
    

    按照正常的逻辑,当线程2修改了flag的值之后,线程1就会退出死循环,从而打印出thread 1 out这句话。实际上运行程序,在打印出thread 2 out这句话之后,程序就会陷入死循环,并不会再打印线程1中的句子。这就是因为线程1读取flag值时,是读取的线程内保存的变量副本的值,这就导致了虽然flag已经修改为了false,但是线程1读到的值仍然是true,也就不会退出循环了。

    要解决这个问题,只需要把flag变量加上volatile修饰符即可。

        private static volatile boolean flag = true;
    

    再运行程序,就会发现可以正常打印出线程1中的句子了。volatile解决的就是类似的在多线程环境下变量的可见性问题。

    二、volatile的作用是什么?

    volatile关键字主要有两点作用:保证可见性、禁止重排序。

    1. 保证可见性

    对于volatile修饰的变量,在读值时会直接读取主内存的值而不是工作线程中的变量副本,在写入时也会刷新主内存中的值,这也就保证了可见性。

    2. 禁止重排序

    对于一些代码,虚拟机会在不影响结果的前提下对字节码进行重排序,从而提高效率。不影响结果是在单线程的环境下,对于多线程

    三、volatile能保证线程安全或者原子性吗?比如i++,能使用volatile来确保线程安全吗?

    volatile能够保证共享变量读或写单步的可见性。但是对于i++来说,并不能使用volatile来保证线程安全。

    参照如下代码:

        private static int i = 0;
    
        public static void main(String[] args) throws InterruptedException {
            Runnable task = () -> {
                for (int j = 0; j < 1000; j++) {
                    i++;
                    Thread.yield();
                }
            };
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.submit(task);
            exec.submit(task);
            exec.shutdown();
            exec.awaitTermination(60, TimeUnit.SECONDS); // 等待任务执行完毕
            System.out.println(i);
        }
    

    通过查看i++的字节码,可以发现它其实是分为4步:

    i++字节码

    其中,getstatic将静态变量i的值入栈,iconst_1将1入栈,iadd将栈顶两个数出栈、并将它们的和入栈,putstatic将栈顶的值赋给变量i

    通过字节码就可以知道为什么i++是线程不安全的:如果两个线程同时执行i++这句代码,有可能线程1将i值入栈之后,线程2抢到CPU时间片,线程2又将i的值入栈——这时候i的值还是原始值,并未改变。这种情况下,执行了两次i++后,i的新值本应是i + 2,结果却是i + 1

    而volatile——保证可见性与禁止重排序——在这个问题上显得毫无用处。所以,volatile并不可以保证线程安全。正确的做法是加锁或者使用Atomic类。

    相关文章

      网友评论

          本文标题:【Java】volatile简述

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