volatile 关键字

x33g5p2x  于2021-12-05 转载在 其他  
字(1.6k)|赞(0)|评价(0)|浏览(488)

volatile 保证内存可见性 (作用)

内存可见性??

举例:

public class ThreadDemo17 {
    static class Counter{
        public int flag = 0;

    }
    public static void main(String[] args) {
        Counter counter = new Counter();

        // 下面两个线程中涉及到的 counter 都是同一个对象
        Thread t1 = new Thread(){
          @Override
          public void run(){
              while (counter.flag == 0){

              }
              System.out.println("循环结束!!");
          }
        };
        t1.start();

        Thread t2 = new Thread(){
            @Override
            public void run(){
                Scanner scan = new Scanner(System.in);
                System.out.print("请输入一个整数: ");
                // 在此处修改 flag,按理说,是会影响到 t1 中的 flag
                counter.flag = scan.nextInt();
            }
        };
        t2.start();
    }
}

预期效果:
线程1,先进入循环;线程2,会读取用户输入的一个整数
随着用户输入了一个非0 的整数后,此时,线程1 的循环就会随之终止

实际效果:

线程2,输出数据完毕后,发现线程1 的循环并没有结束
这是为什么?????

这样的现象背后,涉及到了 编译器的优化

[ 注意 ], 编译器的优化,必须保证一个前提:优化后的逻辑和优化前是等价的
但是此处,编译器错误的优化,导致程序出现了 bug ( 这是编译器自身的"坑"~~ )

上述的优化策略,就是"内存可见性"
若优化生效,内存就是不可见的了 (其他线程修改了也看不见)
若优化不生效,内存才是可见的 (其他线程修改能看见)

因此 volatile 关键字的作用,也就是:禁止编译器进行上述场景的优化
(一个线程读,一个线程写,修改对于读线程来说可能没生效)

故,刚才的代码可以加上 volatile 关键字:

static class Counter{
    public volatile int flag = 0;
}

此时的输出结果:

加上 volatile 关键字,手动禁止了这样的优化

volatile 不保证原子性

在之前的 线程安全篇 里,我们举了一个例子
那么,我们如果加上关键字 volatile,会解决这里的线程不安全问题嘛??

public class ThreadDemo18 {
    static class Counter{
    	// 此处加上 关键字 volatile 
        public volatile int count = 0;

        public void increase(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(){
            @Override
            public void run(){
                for (int i = 0; i < 50000; i++) {
                    counter.increase();
                }
            }
        };
        t1.start();

        Thread t2 = new Thread(){
            @Override
            public void run(){
                for (int i = 0; i < 50000; i++) {
                    counter.increase();
                }
            }
        };
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

执行验证:

我们发现,volatile 关键字,并不能解决这个问题,最终 count 的值仍然无法保证是 100000

线程不安全的场景:
1.一个线程读,一个线程写 ( volatile 关键字解决)
2.两个线程写 (加锁解决线程安全问题)

下篇:通信 — 对象的等待集

相关文章