温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

volatile关键字的作用以及对变量的影响

发布时间:2020-06-08 16:33:50 来源:亿速云 阅读:316 作者:Leah 栏目:编程语言

这篇文章主要为大家分享volatile关键字对普通全局变量的效果影响。其次介绍了volatile关键字的作用,阅读完整文相信大家对volatile关键字了一定的认识。

接下来,我们就一起来分析下这个问题!让我们先通过一个例子来回顾下volatile关键字的作用!

public class VolatitleFoo {
    //类变量
    final static int max = 5;
    static int init_value = 0;

    public static void main(String args[]) {
        //启动一个线程,当发现local_value与init_value不同时,则输出init_value被修改的值
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                if (init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", init_value);
                    //对localValue进行重新赋值
                    localValue = init_value;
                }
            }
        }, "Reader").start();
        //启动updater线程,主要用于对init_value的修改,当local_value=5的时候退出生命周期
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                //修改init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

在上面的代码示例中,我们定义了两个类变量max、init_value,然后在主线程中分别启动一个Reader线程,一个Updater线程。Updater线程做的事情就是在值小于max的值时以每两毫秒的速度进行自增。而Reader线程则是在感知init_value值发生变化的情况下进行读取操作。

期望的效果是线程Updater更新init_value值之后,可以立刻被线程Reader感知到,从而进行输出显示。实际运行效果如下:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

实际的运行效果是在Updater修改类变量init_value后,Reader线程并没有立马感知到变化,所以没有进行相应的显示输出。而原因就在于共享类变量init_value在被线程Updater拷贝到该线程的工作内存中后,Updater对变量init_value的修改都是在工作内存中进行的,完成操作后没有立刻同步回主内存,所以Reader线程对其改变并不可见。

为了解决线程间对类变量init_value的可见性问题,我们将类变量init_value用volatile关键字进行下修饰,如下:

static volatile int init_value = 0;

然后我们再运行下代码,看看结果:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

此时线程Updater对类变量init_value的修改,立马就能被Reader线程感知到了,这就是volatile关键字的效果,可以让共享变量在线程间实现可见,原因就在于在JVM的语义层面要求被volatile修饰的共享变量,在工作内存中的修改要立刻同步回主内存,并且读取也需要每次都重新从主内存中刷新一份到工作内存中后才可以操作。

关于以上适用volatile关键字修饰基本类型的类变量、实例变量的场景,相信大家会比较好理解。接下来,我们来继续改造下代码:

public class VolatileEntity {
    //使用volatile修饰共享资源i
    //类变量
    final static int max = 5;
    int init_value = 0;
    public static int getMax() {
        return max;
    }
    public int getInit_value() {
        return init_value;
    }
    public void setInit_value(int init_value) {
        this.init_value = init_value;
    }
    private static class VolatileEntityHolder {
        private static VolatileEntity instance = new VolatileEntity();
    }
    public static VolatileEntity getInstance() {
        return VolatileEntityHolder.instance;
    }
}

我们将之前代码中的类变量init_value放到实体类VolatileEntity中,并将其设计为一个实例变量,为了便于理解,我们将实体类VolatileEntity设计为单例模式,确保两个线程操作的是同一个共享堆内存对象。如下:

public class VolatileEntityTest {

    //使用volatile修饰共享资源
    private static VolatileEntity volatileEntity = VolatileEntity.getInstance();

    private static final CountDownLatch latch = new CountDownLatch(10);

    public static void main(String args[]) throws InterruptedException {
        //启动一个线程,当发现local_value与init_value不同时,则输出init_value被修改的值
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                if (volatileEntity.init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", volatileEntity.init_value);
                    //对localValue进行重新赋值
                    localValue = volatileEntity.init_value;
                }
            }
        }, "Reader").start();

        //启动updater线程,主要用于对init_value的修改,当local_value=5的时候退出生命周期
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                //修改init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                volatileEntity.init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

在上述代码中线程Updater和Reader此时操作的是类变量VolatileEntity对象中的普通实例变量init_value。在VolatileEntity对象没被volatile关键字修饰之前,我们看下运行效果:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

与未被volatile修饰的int类型的类变量效果一样,线程Updater对VolatileEntity对象中init_value变量的操作也不能立马被线程Reader可见。如果此时我们不VolatileEntity类中单独用volatile关键字修饰init_value变量,而是直接VolatileEntity对象用volatile关键字修饰,效果会如何呢?

private static volatile VolatileEntity volatileEntity = VolatileEntity.getInstance();

此时VolatileEntity对象的引用变量被volatile关键字修饰了,然而其中的普通实例变量init_value并没有直接被volatile关键字修饰,然后我们在运行下代码看看效果:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

从实际的运行效果上看,虽然我们没有直接用volatile关键字修饰对象中的类变量init_value,而是修改了对象的引用,但是我们看到对象中的普通实例变量仍然实行了线程间的可见性,也就是说间接也相当于被volatile关键字修饰了。所以,在这里问题也就基本上有了答案,那就是:“被volatile关键字修饰的对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile关键字修饰了”。

看完上述内容,你们对volatile关键字有进一步的了解吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI