用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!

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

  1. volatile型变量语义讲解一 :对所有线程的可见性

    volatile型变量语义讲解一 :对所有线程的可见性 一.volatile变量语义一的概念 当一个变量被定义成volatile之后,具备两个特性: 特性一:保证此变量对所有线程的可见性.这里的&qu ...

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

    Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量 ...

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

    内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细 ...

  4. synchronized同步块和volatile同步变量

    Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...

  5. Programming In Scala笔记-第五章、Scala中的变量类型和操作

    这一章的一些基础性的东西,主要包括Scala中的基本变量类型,以及相关的一些操作符. 一.简单类型 下表中列出Scala语言中的基本类型,以及其字节长度,其中Byte, Short, Int, Lon ...

  6. seaborn 数据可视化(一)连续型变量可视化

    一.综述 Seaborn其实是在matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易,图像也更加美观,本文基于seaborn官方API还有自己的一些理解.   1.1.样式控制: ...

  7. Java-JUC(十三):现在有两个线程同时操作一个整数I,做自增操作,如何实现I的线程安全性?

    问题分析:正如i在多线程中如果想实现i的多线程操作,必须i要使用volitle来保证其内存可见性,但是i++自增操作不具备原子性操作,因此需要对i++这段代码确保其原子性操作即可. 方案1: 使用Re ...

  8. bool型变量下标的时候javascript是不能允许的

    jother编码是我最开始想写的内容,原因有两点:1.原理比较简单,不需要太多关于算法的知识.2.比较有趣,是在对javascript有了很深的理解之后催生的产物.如果你只需要知道jother编码和解 ...

  9. (转)C语言16进制输出字符型变量问题

    最近在做一个C的嵌入式项目,发现在C语言中用printf()函数打印字符型变量时,如果想采用"%x"的格式将字符型变量值以十六进制形式打印出来,会出现一个小问题,如下: char  ...

随机推荐

  1. C#中文乱码转换

    string text="中文";string keyword;byte[] buffer= Encoding.UTF8.GetBytes(text);keyword=Encodi ...

  2. JVM垃圾收集规则和算法

    1.垃圾收集 Garbage Collection 程序计数器.虚拟机栈.本地方法栈这三部分内存随着线程生而生,随着线程灭而自然的回收,他们的大小在编译期间就大致确定了下来,所以对这部分的回收是具备确 ...

  3. selenium TestNG 依赖和忽略测试

    依赖:通过使用Test 注释的dependsOnMethods={"verifyLogin"}子句,verifyAccountInfo 测试指定了它依赖verifyLogin()方 ...

  4. Selenium2+python自动化36-判断元素存在【转载】

    前言 最近有很多小伙伴在问如何判断一个元素是否存在,这个方法在selenium里面是没有的,需要自己写咯. 元素不存在的话,操作元素会报错,或者元素有多个,不唯一的时候也会报错.本篇介绍两种判断元素存 ...

  5. python的上下文管理(context)(1)

    本文转载自:http://blog.csdn.net/G_66_hero/article/details/53048540 什么是Python中的上下文管理器 怎么使用上下文管理器 如何创建自己的上下 ...

  6. sqlalchemy源代码阅读随笔(1)

    今天看的,是url.py模块,这个在create_engine中,起到的最用很大,其本质,就是对访问数据库的url,进行操作管里.我们可以直接访问这个类. 看一个简单的代码: from sqlalch ...

  7. redis的持久化(RDB&AOF的区别)

    RDB 是什么? 在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里. Redis会单独创建(fork)一个子进程来进行持久化,会 ...

  8. MyEclipse的破解代码,适用各个版本

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public ...

  9. linux ssh 登录服务器失败,密码明明没错【解决】

    本来这样登录的: $ ssh 123.123.123.123 //ssh + IP 然后输入密码就是登录不了,显示permision denied 后来使用如下方式登录,成功! $ ssh -v us ...

  10. python 去掉所有空白字符【解决】

    今天用python从access数据库读取内容,组合成sql语句时,空白字符把我给搞疯了.... 所幸找到了一个好办法: ''.join(s.split())