JDK Client & Server 变量可见性问题

本文探讨了一位开发者遇到的在JDK Client与Server模式下,代码中成员变量处理的不同,导致程序运行结果不一致的问题。通过环境排查发现,问题在于JVM的Client和Server模式对变量优化的差异,Server模式为了性能可能不保证多线程环境中的变量一致性,需要手动添加`volatile`关键字来确保线程安全。文章强调了理解和适配不同JVM模式的重要性。
摘要由CSDN通过智能技术生成

JDK Client & Server 变量可见性问题

下面主要是引出不同jdk对成员变量的处理

背景

一天在群里吹逼的时候,有位老哥发了一段代码出来,他说他运行的结果和网上的不一致。然后我们分别在各自的环境运行了,我们的结果和网上的运行一致。代码如下(你们可以拷贝到自己环境测试一下,大概率都是第二种结果):

public class VisibilityTest {

    private boolean flag = true;

    private int i = 0;

    public void refresh() {
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
    }

    public void load() {
        System.out.println(Thread.currentThread().getName() + "开始执行.....");
        System.out.println(Thread.currentThread().getName() + "修改flag:"+flag +",i:"+i);
        while (flag) {
            i++;
            //TODO 业务逻辑
        }
        System.out.println(Thread.currentThread().getName() + "跳出循环: i=" + i);
    }
    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();
        Thread threadA = new Thread(() -> test.load(), "ThreadA");
        threadA.start();
        Thread.sleep(100);

        Thread threadB = new Thread(() -> test.refresh(), "ThreadB");
        threadB.start();
    }
}

那位老哥运行结果如下(会跳出循环,程序马上停止运行):

ThreadA开始执行.....
ThreadA修改flag:true,i:0
ThreadB修改flag:false
ThreadA跳出循环: i=93402143

我们正常人运行的结果如下(不会跳出循环,程序一直执行):

ThreadA开始执行.....
ThreadA修改flag:true,i:0
ThreadB修改flag:false

分析问题

对于这种问题,第一反应肯定就是环境原因,因为代码都是一致的。那么究竟是哪一个不一致导致的呢?

  1. 对比jdk版本。发现都是jdk1.8,排除了。。这就奇怪了,究竟还有什么是不一样的呢??

我叫那位老哥敲一下:java -version试试:

image.png

然后,另外一个老哥发现了端倪。

image (1).png

我们的都是Server 版本,那位老哥的是Client版本。

然后我就拿老哥的Client版本试了下,然后运行结果和他的一致了。

破案--JDK Client & Server 模式下对变量不同的处理

参考连接:

jvm - Real differences between "java -server" and "java -client"? - Stack Overflow

翻译:

调试提示:对于服务器应用程序,请确保在调用JVM时始终指定-server JVM命令行开关,即使在开发和测试时也是如此。 服务器JVM比客户机JVM执行更多的优化,比如从循环中提升那些在循环中没有修改的变量; 可能在开发环境(客户机JVM)中工作的代码可能在部署环境(服务器JVM)中中断。 例如,如果我们在清单3.4中“忘记”将睡着的变量声明为volatile,那么服务器JVM可以将测试提升出循环(将其变成一个无限循环),但客户机JVM不会。 在开发中出现的无限循环比只在生产中出现的无限循环的成本要低得多。

volatile boolean asleep;
...
while (!asleep)
   countSomeSheep();
个人见解

我认为,Server模式比Client 的变量做了更多性能的优化,比如这个成员变量。Server为了更高的性能,貌似不会保证成员变量在多线程环境下的一致性,需要指明:volatile才能保证多线程环境下的一致。(多线程在不同的核心进行计算,变量是在寄存器中参与运算的,不同核心运算(多线程下)之间的运算会有变量的副本,这就导致了不同线程实际上操作的变量不是同一份,这就是线程不安全,但是把寄存器的变量刷回主存,同时还需要对其他线程加锁才行,性能比较低,所以Server就默认采用了"牺牲一致性,保证性能"的方案把,Server需要手动添加volatile保证线程安全。目测Client 版本牺牲了性能保证了一致性,所以多线程操作的相当于操作同一份变量。)

总结

同一份代码,不同机器运行结果不一致,大概率是环境问题。需要排查jdk版本。

计算机中都是讲究平衡。要性能就要牺牲其他东西,牺牲空间,牺牲一致性等等。。Server就是牺牲了一致性换取了性能的提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值