您当前的位置:首页 > 计算机 > 编程开发 > Java

volatile关键字的使用

时间:02-09来源:作者:点击数:

示例代码如下:

public class Main {
    static boolean run = true;
    static DateFormat format = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            long start = System.currentTimeMillis();
            while (run) {
                if (System.currentTimeMillis() - start >= 2000) {
                    start = System.currentTimeMillis();
                    System.out.println("当前时间:" + format.format(start));
                }
            }
        }).start();

        Thread.sleep(7000);
        run = false;
        System.out.println("已经设置run = " + run  + ",当前时间:" + format.format(System.currentTimeMillis()));
    }

}

运行结果如下:

当前时间:17:26:27
当前时间:17:26:29
当前时间:17:26:31
已经设置run = false,当前时间:17:26:32
当前时间:17:26:33

代码的功能为,在子线程中是一个死循环,每两秒打印一下当前时间,在main线程中睡眠7秒,则理论上子线程可以打印3个时间,因为每两秒打印一个,6秒打印3个,第7秒的时候main线程把run变量设置为false,理论上此时while循环就结束了,但是并没有,while循环一直在转,直到第8秒的时候打印了第4个时间之后while循环才结束,也就是说在子线程中,第7秒到第8秒的时候,while语句拿到的run变量一直是true,所以才没有退出循环。

同样的代码,复制到Android项目中运行又没这个问题。神奇。

问了公司同事,说是每个线程在访问run变量时,都会拷贝一份副本到各自线程的堆栈中,所以我们在主线程中修改run变量只是修改了一个副本,而子线程中的run是另一个副本,没有得到及时的更新,所以才出现了问题,解决方案就是在变量上加入volatile修饰符,如下:

volatile static boolean run = true;

再次运行就没问题了。volatile的功能可以简单的理解为不再使用副本了,所以不会有之前的问题。

如果不加volatile修饰符,代码稍作修改又没问题了,如下:

while (run) {
    if (System.currentTimeMillis() - start >= 2000) {
        start = System.currentTimeMillis();
        System.out.println("当前时间:" + format.format(start));
    }
    
    if (run) {
    
    }
}

代码很简单,就是在while中加入了一个if判断,其它代码都不变,但是运行是OK。或者我们把while获取时间的代码提取到一个end变量,然后运行也是没问题的,如下:

while (run) {
    long end = System.currentTimeMillis();
    if (end - start >= 2000) {
        start = System.currentTimeMillis();
        System.out.println("当前时间:" + format.format(start));
    }
}

至于这些神奇的现象,我们就不管它了,总之就记得,在使用多线程的时候,如果多个线程要访问同一个数据,这个数据就加volatile修饰,这个数据一般指8大基本数据类型,如果是对象类型,可以不用加的,因为一般对象我们创建后就是访问这个对象中的属性,很少会再创建一个新的对象。

在懒汉式单例中,也是需要加入volatile修饰的,示例如下:

public class Singleton {
    
    private volatile static Singleton instance;    
    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }        
        return instance;
    }
    
}

如果我们没加volatile的话,IntelliJ也会提示我们加的,如下:

在这里插入图片描述

对于基本数据类型的属性,如果需要在多线程中又读又写的话,尽量使用对应的同步对象:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

这样的话就不需要加volatile修饰了,因为这些对象里面包装的基本数据已经加入了volatile修饰了。如果我们查看它们的源码,会发现对应的getset方法并没有加入同步代码块,因为它包装的value已经是volatile修饰的了,所以简单的一个set赋值,其实就是一行语句的执行,没必要加入同步代码,影响效率,对于AtomicBoolean,它封装的value是用int类型的,用0表示false,1表示true。在addAndGet函数上,还看到了Unsafe的使用,据说它是sun公司留的后门,用这个类可以申请内存,且不受jvm控制,具体可百度。

使用AtomicBoolean修改前面的Demo,如下:

public class Main {
    static AtomicBoolean run = new AtomicBoolean(true);
    static DateFormat format = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            long start = System.currentTimeMillis();
            while (run.get()) {
                if (System.currentTimeMillis() - start >= 2000) {
                    start = System.currentTimeMillis();
                    System.out.println("当前时间:" + format.format(start));
                }
            }
        }).start();

        Thread.sleep(7000);
        run.set(false);
        System.out.println("已经设置run = " + run  + ",当前时间:" + format.format(System.currentTimeMillis()));
    }

}

kotlin中,没有volatile关键字,可以在变量上加@Volatile注解实现同样的功能。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门