栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

volatile关键字的使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

volatile关键字的使用

示例代码如下:

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修饰了。如果我们查看它们的源码,会发现对应的get和set方法并没有加入同步代码块,因为它包装的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注解实现同样的功能。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/821294.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号