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!

时间: 2024-10-19 13:01:05

volatile型变量自增操作的隐患的相关文章

Java虚拟机内存模型和volatile型变量

Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量会保存在主内存中(Main Memory). 而线程共享的变量的副本会保存在每个线程各自的工作内存中(Working Memory). 线程对于共享变量的所有操作(读取,赋值等)都必须在工作内存中进行,不能直接读写主内存的变量. 不同的线程之间,也无法访问其他线程的工作内存.线程之间的变量传递需要通过主内存来

Java线程角度的内存模型和volatile型变量

内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细节. Java内存模型规定所有的变量都存储在住内存,每条线程还有自己的工作内存,工作内存保存了被该线程使用到的变量和主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成. 关键

java中volatile型变量 说明

volatile变量具有两种特性: 保证此变量对所有线程的可见性,注意:java里的运算不是原子操作,导致volatile变量在并发下存在不一致的问题. 使用规则要求 a. 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值. b. 变量不需要与其他的状态变量共同参与不变约束. 禁止指令重排序优化 其他说明: Java内存模型中定义以下八种操作(具有原子性)完成: 1.lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态. 2.unlock(解锁):作用于

从头认识多线程-3.2 使用volatile声明的变量的写操作是非原子性的

这一章节我们来讨论一下使用volatile声明的变量的各种操作是非原子性的. 1.上一章节我们已经提到,volatile把工作内存里面变量的改变同步到主内存, 使得各个线程能够把该变量当成是整体的状态控制 2.但是,使用volatile声明的变量的写操作是非原子性的 代码清单: package com.ray.deepintothread.ch03.topic_2; public class VolatileTest extends Thread { private volatile int i

synchronized同步块和volatile同步变量

阅读目录 synchronized同步块 volatile同步变量 Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错. 回到顶部 synchronized同步块 Java中的同步块用synchronized标记.同步块在Java中是同步在某个对象上.所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作.所有其他等待进

C++中将string类型变量转换成int型变量

需要的头文件:#include<sstream> 操作: string s1="124": int x; stringstream ss; ss<<s1; ss>>x; C++中将string类型变量转换成int型变量,布布扣,bubuko.com

const型变量和const型指针

const型变量和const型指针 const型变量 如果在程序中需要经常使用某一常量,可以使用编译预处理命令定义符号常量. eg:#define PI 3.14159 const型变量也是一种定义常量的手段.const型变量是特殊的变量,程序运行过程中不可改变,其他特性和变量一样.定义const变量时必须初始化. eg: const int Max=1000; //int和const位置可以交换. const常量与符号常量的区别: 1)符号常量由预编译器处理,const型变量由变量处理. 2)

字符型变量

我们都知道,除了数字以外还有其他符号,比如字母等等,这些符号就是字符,存储字符的变量就是字符型变量. 一个字符型变量只能存储一个字符,比如time这个单词,就需要四个字符变量来存储. 那么,思考下面几个问题: 字符型变量在计算机内是以什么形式存储的? 答案很显然,也是0110这类的二进制码. 那么,二进制码是怎么变成字符的? 这个问题就比较专业了,就是用到ASCII码.简单地说,就是哪个数字代表哪个字符都统一标准.比如65就代表A,97代表a. 所以实际上,字符型就是个整数. 字符型的定义: c

C语言中以十六进制输出字符型变量会出现&#39;ffffff&quot;的问题

最近在做一个C的嵌入式项目,发现在C语言中用printf()函数打印字符型变量时,如果想采用"%x"的格式将字符型变量值以十六进制形式打印出来,会出现一个小问题,如下: C代码   char buf[10] = {0}; buf[0] = 0xbf; printf("%2x\n\n\n", buf[0]);            /*在终端将会显示成:ffffffbf*/ buf[1] = 0x7f; printf("%2x\n\n\n", bu