java volatile关键字 深入理解

发布时间:2023年12月28日

简述特性

在Java中,volatile 是一个关键字,用于修饰类的实例变量。volatile 的主要作用是保证多个线程能够正确地处理被多个线程共享的变量。当一个变量被声明为volatile时,它具有以下两个特性:

  • 可见性(Visibility): 当一个线程修改了volatile变量的值时,这个新值会立即被其他线程看到。这是因为volatile变量在主存中保存,每次使用之前都会从主存中读取最新的值,而不是使用线程本地的缓存。
  • 禁止指令重排序(Atomicity): volatile关键字禁止指令重排序,确保对volatile变量的操作是原子的。这就意味着volatile变量的读和写是不可分割的操作,不会被其他线程的操作中断。

尽管volatile提供了一定程度的线程安全,但它并不能完全替代锁。volatile适用于那些变量的写操作不依赖于当前值的情况,例如标志位的修改。但对于一些复合操作,仍然需要使用锁来确保原子性。

需要注意的是,volatile并不能保证原子性,因此不能替代synchronized关键字。如果需要确保一系列操作的原子性,还是需要使用锁。

问题示例

示例一

public class NonVolatileExample {
    private boolean flag = false;

    public static void main(String[] args) {
        NonVolatileExample example = new NonVolatileExample();

        // 线程1:修改flag的值
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.setFlag(true);
            System.out.println("Flag is set to true by Thread 1");
        });

        // 线程2:检查flag的值
        Thread thread2 = new Thread(() -> {
            while (!example.isFlag()) {
                // 等待flag变为true
            }
            System.out.println("Flag is true, Thread 2 can proceed");
        });

        thread1.start();
        thread2.start();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

当运行上述示例,会在while (!example.isFlag())处一直卡着,说明线程thread2读取flag一直读取的是旧值

示例二

public class NonVolatileExample {
    private boolean flag = false;

    public static void main(String[] args) {
        NonVolatileExample example = new NonVolatileExample();

        // 线程1:修改flag的值
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.setFlag(true);
            System.out.println("Flag is set to true by Thread 1");
        });

        // 线程2:检查flag的值
        Thread thread2 = new Thread(() -> {
            while (!example.isFlag()) {
                // 等待flag变为true
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Flag is true, Thread 2 can proceed");
        });

        thread1.start();
        thread2.start();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

当在线程thread2中加上Thread.sleep(1000)就没有出现while中长时间等待的情况,这里说明thread2线程还是可以读取到thread1修改后的数据,只是什么时候放弃缓存读取主存的时机不由java程序控制,是由操作系统及计算机硬件决定,所以会导致示例一的情况,thread1修改过很长时间后thread2才能读取到共享对象

使用示例

public class NonVolatileExample {
    private volatile boolean flag = false;

    public static void main(String[] args) {
        NonVolatileExample example = new NonVolatileExample();

        // 线程1:修改flag的值
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.setFlag(true);
            System.out.println("Flag is set to true by Thread 1");
        });

        // 线程2:检查flag的值
        Thread thread2 = new Thread(() -> {
            while (!example.isFlag()) {
                // 等待flag变为true
            }
            System.out.println("Flag is true, Thread 2 can proceed");
        });

        thread1.start();
        thread2.start();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

加上volatile关键字后java就能控制读取主存的时机

文章来源:https://blog.csdn.net/qq_44272797/article/details/135256531
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。