美文网首页
volatile关键字

volatile关键字

作者: sunpy | 来源:发表于2023-03-23 15:00 被阅读0次

    多线程下的共享变量的问题


    1. 多个线程操作同一变量数据,是否会看到变化的数据。
    2. 多个线程执行同一段程序,会不会中间插手,造成脏数据(多数情况下,我们通过加锁的形式来保证一段程序一个时间段只有一个线程执行)。
    3. 多线程操作时,编译器出于性能考虑,采取指令重排序(处理器的乱序执行),导致多线程程序出现问题
      (指令重排序是编译器处于性能考虑,在不影响单线程程序正确性的
      条件下进行重新排序)。

    \color{red}{简单概括就是多线程存在可见性、原子性、有序性的问题。}

    volatile实现可见性


    未加入volatile关键字引发的可见性问题:

    private static boolean flag=false;
    
    public static void test1() {
        System.out.println("========== main thread start test1");
    
        Thread t1 = new Thread(() -> {
            while (!flag) {
    
            }
            System.out.println("========== " + Thread.currentThread().getName() + " - flag : " + flag);
        }, "t1");
        t1.start();
    
        //主线程睡眠100毫秒
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        Thread t2 = new Thread(() -> {
            flag = true;
            System.out.println("==========" + Thread.currentThread().getName() + " - flag : " + flag);
        }, "t2");
        t2.start();
    }
    

    结果:线程间数据不可见

    加入volatile关键字:

    private static volatile boolean flag=false;
    

    结果:

    volatile防止指令重排序


    未加入volatile关键字引发的指令重排序问题:

    private static boolean flag=false;
    private static int i = 0;
    public static void test2() throws InterruptedException {
        System.out.println("========== main thread start test2");
    
        for (int a = 0; a < 100000; a++) {
            Thread t1 = new Thread(() -> {
                i = 1;
                flag = true;
            }, "t1");
    
            Thread t2 = new Thread(() -> {
                if (flag) {
                    int j = i * i;
                    System.out.println("========== " + Thread.currentThread().getName() + " - j = " + j);
                }
            }, "t2");
    
            t1.start();
            t2.start();
    
            i = 0;
            flag = false;
        }
    }
    

    配置输出到指定的文件:

    结果:线程t1中,flag=true居然先于i=1先执行,打印出来了0。

    加入volatile关键字:

    private static volatile boolean flag = false;
    

    结果:

    为什么在变量flag上面加volatile关键字,而不是在变量i上面?
    这就涉及到volatile的内存屏障,简单来说就是:

    • volatile变量的写,不允许与前面普通变量的写重排序。
    • volatile变量的写,不允许与后面普通变量的写/读重排序。
    • volatile变量的读,不允许与后面普通变量的写/读重排序。

    \color{red}{volatile可以实现可见性、有序性,但是无法提供原子性。}
    \color{green}{volatile想要实现原子性,需要配合synchronized锁或者cas锁。}

    多线程下的单例模式(double-check-locking双重检查锁定)


    思路:利用volatile+synchronized 保证原子性、可见性、有序性

    public class SingleObj {
    
        private static volatile SingleObj singleObj = null;
    
        private SingleObj() {
        }
    
        public static SingleObj getInstance() {
            if (null == singleObj) {
                synchronized (SingleObj.class) {
                    if (null == singleObj) {
                        singleObj = new SingleObj();
                    }
                }
            }
    
            return singleObj;
        }
    }
    

    解释:

    • 加入synchronized关键字是为了保证多线程不会乱序操作,造成数据混乱。
    • volatile关键字是为了保证多个线程间的可见性的同时为了保证实例化和赋值的操作不会重排序,防止极端情况下先赋值,后实例化。
    • 至于内层的if判断,主要还是为了挡住阻塞的线程涌入,直接造成数据非单例了。

    多线程下的卖票(cas工具类实现)


    public class AiTest {
    
        private static AtomicInteger atomicInteger = new AtomicInteger(100);
    
        public static void test4() {
            for (int i = 0 ; i < 100 ; i++) {
                Thread t = new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + atomicInteger.getAndDecrement() + "张票");
                    ;
                }, "t");
                t.start();
            }
        }
    
        public static void main(String[] args) {
            test4();
        }
    }
    

    参考


    https://blog.csdn.net/weixin_42867178/article/details/124431933
    https://zhuanlan.zhihu.com/p/133851347

    相关文章

      网友评论

          本文标题:volatile关键字

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