volatile型变量自增操作的隐患
2016-11-23 11:44
232 查看
用FindBugs跑自己的项目,报出两处An increment to a volatile field isn’t atomic。相应报错的代码如下:
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验证这个问题
执行结果
自增操作总体上产生了1%的误差。FindBugs是个好工具,能找出自己知识体系范围外的bug,surprise!
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型变量自增操作的隐患
- Symbian中的iEikonEnv和iCoeEnv变量以及文件操作
- CodeIgniter框架——访问方式 URI 分配变量 数据库操作
- Linux shell编程(一)变量与字符串操作
- 多线程下变量-原子操作 sync_fetch_and_add等等
- PHP内核探索之变量(4)- 数组操作
- C#中为多线程变量提供原子操作的类Interlocked
- 位操作:如何将一个变量其中一位清0,置1,取反
- Windows下面对环境变量的操作
- shell变量的操作
- 嵌入式 多线程下变量-原子操作__sync_fetch_and_add等等
- 【实战Java高并发程序设计】5:让普通变量也享受原子操作
- Java线程角度的内存模型和volatile型变量
- 基本数据类型操作三:float和double变量的赋值
- 重写——上转型对象不能操作子类新增成员变量和方法但可以操作子类继承和重写的方法
- linux 环境变量笔记及bash操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 变量子串的常用操作
- Spark性能优化(2)——广播变量、本地缓存目录、RDD操作、数据倾斜