zoukankan      html  css  js  c++  java
  • volatile型变量自增操作的隐患

      用FindBugs跑自己的项目,报出两处An increment to a volatile field isn’t atomic。对应报错的代码例如以下:

    volatile int num = 0;
    num++;

      FindBugs针对这样的类型的错误给出了对应的解释

    An increment to a volatile field isn’t atomic
    This code increments a volatile field. Increments of volatile fields aren’t atomic. If more than one thread is incrementing the field at the same time, increments could be lost.

      意即,对一个volatile字段进行自增操作。但这个字段不是原子类型的。假设多个线程同一时候对这个字段进行自增操作。可能会丢失数据。


      volatile是一个轻量级的synchronized的实现,针对volatile类型变量的操作都是线程安全的。volatile类型变量每次在读取的时候。都从主存中取。而不是从各个线程的“工作内存”。

    而非volatile型变量每次被读取的时候都是从线程的工作内存中读取主存中变量的一份拷贝。也就意味着假设非volatile型变量被某个线程改动,其他线程读取的可能是旧值。


    jvm内存模型图
    这里写图片描写叙述

      volatile类型变量每次在读取的时候,会越过线程的工作内存,直接从主存中读取。也就不会产生脏读。

    那为何FindBugs报这个错?

      根本原因在于++自增操作。Java的++操作对应汇编指令有三条
      1. 从主存读取变量值到cpu寄存器
      2. 寄存器里的值+1
      3. 寄存器的值写回主存

      假设N个线程同一时候运行到了第一步。那么终于变量会损失(N - 1)。第二步第三步仅仅有一个线程是运行成功。
      写个demo验证这个问题

    package com.alibaba.bop.tag.manager;
    
    import org.junit.Test;
    
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * author   : lvsheng
     * date     : 2016/11/22 下午5:06
     */
    public class volatileTest {
    
        volatile int num = 0;
        int                coreSize = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor exec     = new ThreadPoolExecutor(coreSize * 2, coreSize * 3, 0, TimeUnit.SECONDS,
                                                                    new LinkedBlockingQueue<Runnable>(500), new ThreadPoolExecutor.CallerRunsPolicy());
    
        @Test
        public void test() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                exec.execute(() -> num++);
            }
            System.out.println(Integer.MAX_VALUE);
            System.out.println(num);
            System.out.println("误差 : " + (Integer.MAX_VALUE - num));
        }
    }

      运行结果

    2147483647
    2121572795
    误差 :25910852

      自增操作整体上产生了1%的误差。FindBugs是个好工具。能找出自己知识体系范围外的bug。surprise!

  • 相关阅读:
    忘记的知识点补充
    mysql使用过程中出现的问题总结
    身份证校验
    数据库plsql配置
    前端字符间间距与单词间间距
    Oracle中的instr()函数 详解及应用
    Oracle执行过程中出现的问题
    572. Subtree of Another Tree 大树里包括小树
    404. Sum of Left Leaves 左叶子之和
    637. Average of Levels in Binary Tree 二叉树的层次遍历再求均值
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8724004.html
Copyright © 2011-2022 走看看