• synchronized下的 i+=2 和 i++ i++执行结果居然不一样


    起因

    逛【博客园-博问】时发现了一段有意思的问题:

    问题链接:https://q.cnblogs.com/q/140032/

    这段代码是这样的:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class AutomicityTest implements Runnable {
    
        private int i = 0;
    
        public int getValue() {
            return i;
        }
    
        /**
        * 同步方法,加2
        */
        public synchronized void evenIncrement() {
            i += 2;
            // i++;
            // i++;
        }
    
        @Override
        public void run() {
            while (true) {
                evenIncrement();
            }
        }
    
        public static void main(String[] args) {
    
            ExecutorService exec = Executors.newCachedThreadPool();
    
            // AutomicityTest线程
            AutomicityTest automicityTest = new AutomicityTest();
            exec.execute(automicityTest);
    
            // main线程
            while (true) {
                int value = automicityTest.getValue();
                if (value % 2 != 0) {
                    System.out.println(value);
                    System.exit(0);
                }
            }
        }
    }

    代码很简单(一开始我没看清楚问题,还建议楼主去学习下Java语法,尴尬的一批~~~)

    简单说明下代码逻辑

    一、AutomicityTest类实现了Runnable接口,并实现 run() 方法,run() 方法中有一个 while(true) 循环,循环体中调用了一个 synchronized 修饰的方法 evenIncrement();

    二、AutomicityTest类中有一个 int i 成员变量;

    三、在 main() 方法中使用线程池执行线程(直接 new Thread().start() 是一样的),然后 while(true) 循环体中不停打印成员变量 i 的值,如果是奇数就退出虚拟机。

    大家觉得这段代码会输出什么呢?

    没错就是死循环!!!

    有意思的地方来了,如果我把同步方法 evenIncrement() 改为下面这样:

    public synchronized void evenIncrement() {
        // i += 2;
        i++;
        i++;
    }

    执行结果是退出了虚拟机!!!

    是不是很纳闷儿,要是不纳闷儿,就不用往下看了 >_<

    一起来分析一下

    1.首先从方法入口开始,main() 方法当中创建了一个AutomicityTest线程 和 本身 main() 所在的main线程,所以AutomicityTest线程是一个写线程,main线程是一个读线程,既然有读有写,又是多个线程,就涉及到工作内存和主存模型,在这里我就不赘述了。

    2.写线程是有synchronized修饰的,但是读线程并没有,这就导致了读写不一致,解决方法就是给 getValue() 加上synchronized,此时执行结果就正常了

    3.但是问题还没完,为什么 i += 2 死循环,而 【两条】 i++ 却退出了虚拟机呢?

    字节码层面分析

    public synchronized void evenIncrement() {
        i += 2;
    }
    
    // 对应的字节码
    public synchronized void evenIncrement();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field i:I
             5: iconst_2
             6: iadd
             7: putfield      #2                  // Field i:I
            10: return
    public synchronized void evenIncrement() {
        i++;
        i++;
    }
    
    // 对应的字节码
    public synchronized void evenIncrement();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field i:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field i:I
            10: aload_0
            11: dup
            12: getfield      #2                  // Field i:I
            15: iconst_1
            16: iadd
            17: putfield      #2                  // Field i:I
            20: return

    i+=2;

    字节码指令只有一段putfield,没有执行是偶数,执行了也是偶数,所以会死循环。

    i++; i++;

    字节码指令有两段putfield,由于之前getValue()没有加synchronized,那么在执行getValue()的时候,putfield可能没有执行,可能执行了一次,也可能执行了两次,没有执行是偶数,执行一次是奇数,执行两次是偶数;又因为AutomicityTest线程 run() 是 while (true) {} 的,所以它总能执行到奇数,退出虚拟机。

    最后

    到此就分析完了,所以该问题的关键就是写操作是原子的,但是读操作不是,导致读出来的数据不是最终的。


    __EOF__

  • 本文作者: Yu Shi xin
  • 本文链接: https://www.cnblogs.com/yushixin1024/p/16368639.html
  • 关于博主: 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。
  • 版权声明: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在明显位置给出原文连接,否则保留追究法律责任的权利。
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    gradle8.0或者其他版本下载太慢或者time out超时(完美解决方法)
    CentOS 6服务器升级Git时, 下载依赖包失败,需配置国外Yum源
    vue使用pcl.js展示.pcd/.bin文件代码源码
    知识图谱实战应用28-基于py2neo的ICD-11疾病分类的知识图谱的查询与问答实战应用
    微服务:高性能网关 ShenYu简介
    vite搭建vue3项目
    Fiddler(八) - 抓取手机APP的流量-插件Fiddler Orchestra Beta安装&配置
    HTTPS协议详解:基本概念与工作原理
    Java面试中的常问的多线程问题
    Semaphroe + CountDown
  • 原文地址:https://www.cnblogs.com/yushixin1024/p/16368639.html