Java原子类AtomicInteger实现原理的一点总结
java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类:
java.util.concurrent.atomic.AtomicBoolean
java.util.concurrent.atomic.AtomicInteger
java.util.concurrent.atomic.AtomicIntegerArray
java.util.concurrent.atomic.AtomicIntegerFieldUpdater
java.util.concurrent.atomic.AtomicLong
java.util.concurrent.atomic.AtomicLongArray
java.util.concurrent.atomic.AtomicLongFieldUpdater
java.util.concurrent.atomic.AtomicMarkableReference
java.util.concurrent.atomic.AtomicReference
java.util.concurrent.atomic.AtomicReferenceArray
java.util.concurrent.atomic.AtomicReferenceFieldUpdater
java.util.concurrent.atomic.AtomicStampedReference
java.util.concurrent.atomic.DoubleAccumulator
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.LongAdder
普通的自增减(value++或者value--)操作为非原子操作,但是借助原子类包装的自增减操作的保证了原子性。
测试代码:
package com.demo;
import java.util.concurrent.atomic.AtomicInteger;public class TestAtomicInteger {
public static AtomicInteger value = new AtomicInteger(0);//原子类实例
// public static int value = 0;
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(){
public void run() {
for (int j = 0; j < 100; j++) {
value.incrementAndGet();
// value++;
}
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(value);
}
}
这是一段经典的多线程访问共享变量的实现线程安全的例子。
如果采用注释的两处代码public static int value = 0;和value++;替换相应的代码,则会出现线程安全的问题,即使将value变量用volatile修饰保证其可见性,但是由于value++本身非原子性,仍然是线程不安全的。
重点是探索一下保证原子性操作的实现过程。
首先AtomicInteger类引入了Unsafe类,该类的路径为sun.misc.Unsafe。实际上,上面的大部分原子类都import了该类。
在AtomicInteger类内部,通过一个Unsafe类型的静态不可变的变量unsafe来引用Unsafe的实例。
private static final Unsafe unsafe = Unsafe.getUnsafe();
然后,AtomicInteger类用value保存自身的数值,并用get()方法对外提供。
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
...
...
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
有了如上前提,继续往下
AtomicInteger类的incrementAndGet()方法源码如下:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
current保存当前值,这个值在后面会作为一个是否有其他线程改变value值的依据。如果没有其他线程更改value值,那么期望上前后两个时间点获取的value值应该保持不变。next保存自增1后的值,这个值是可能被更新到value的值,如果compareAndSet(current, next)返回真,自增成功。如果返回为false,表示设置不成功,可能是其他线程更改了共享变量,导致current失效,此时再次发起一轮循环。。
compareAndSet()的源码如下:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
直接调用了unsafe对象的compareAndSwapInt()方法,再一次追踪该方法:
该方法位于sun.misc.Unsafe类中:
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
发现该方法是native方法。
而这些诸如compareAndSwapInt(),compareAndSwapLong(),compareAndSwapObject()等的方法由虚拟机内部对其做了特殊的处理,即时编译出来的结果就是一条平台相关的处理器CAS(Compare-and-Swap)指令,该CAS指令甚至无法通过javap解析字节码体现出来。
可以看出,原子类实现的自增操作可以保证原子性的根本原因在于硬件(处理器)的相关指令支持。将语义上需要多步操作的行为通过一条指令来完成,CAS指令可以达到这个目的。
CAS指令需要三个操作数:内存位置,旧预期值和新值。CAS要求,当且仅当当前内存位置处的值等于旧预期值时,就用新值更新当前内存位置处的值,否则它就不执行更新操作,整个过程正好映射了比较-交换(或者说比较-更新)的概念,同时处理过程是一个原子操作。
如果要进行一个参数对应,CAS指令需要的三个操作数:(内存位置,旧预期值和新值)可以分别对应compareAndSwapInt()方法的后三个参数:(long offset,int expected,int x)。此处的expected也即是current的值,x也即是next的值。
当然CAS指令的原子操作还存在一个“ABA”问题,大意即使讲,在某个线程准备进行检测时,如果其间其他线程将一个共享变量的值进行了多次更改后又回到了初始的值,此时该线程通过CAS检测会认为该共享变量未发生更改,这与实际情况不符合。
通过原子类实现线程安全是非阻塞的(对比于synchronized关键字)。其基本的思想是基于冲突检测与循环重试。具体讲就是,需要对共享数据修改时,不加锁先进行目标操作,如果发现有其他线程对同一份共享数据做修改(对应于检测到当前内存位置处的值与旧预期值不等),则放弃本次修改,重写循环再次检测并尝试修改,直到成功为止。
synchronized关键字的时间体现了悲观锁的策略思想,而原子类的实现则体现见了乐观锁的思想。
题外话:
上文提到的Unsafe类是不能被用户源程序直接加载和实例化的,因为其构造器被限定为Unsafe类私有,Unsafe只提供getUnsafe()接口间接的对外提供Unsafe的实例,但即使是这样,getUnsafe()方法对调用者要求任然颇为严苛:
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
而位于sun.misc.VM类的isSystemDomainLoader(loader)方法只有在参数loader为启动加载器时,才返回true。
/**
* Returns true if the given class loader is in the system domain
* in which all permissions are granted.
*/
public static boolean isSystemDomainLoader(ClassLoader loader) {
return loader == null;
}
结合上述两个方法可知,通常我们用户程序的加载器为应用程序加载器,直接调用Unsafe是会抛异常的。
完结~~~
转载请注明原文地址:http://www.cnblogs.com/qcblog/p/7750388.html
参考资料:
1、深入理解Java虚拟机:JVM高级特性与最佳实践
Java原子类AtomicInteger实现原理的一点总结的更多相关文章
- 对Java原子类AtomicInteger实现原理的一点总结
java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...
- Java原子类及内部原理
一.引入 原子是世界上的最小单位,具有不可分割性.比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作.再比如:a++: 这个操作实际是a = a + ...
- java的原子类 AtomicInteger 实现原理是什么?
采用硬件提供原子操作指令实现的,即CAS.每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全. CAS机制 CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换. ...
- Java原子类中CAS的底层实现
Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...
- 源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)
一.前言 前一阵子比较好奇,想看到底层(虚拟机.汇编)怎么实现的java 并发那块. volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看. 但是原子 ...
- java:原子类的CAS
当一个处理器想要更新某个变量的值时,向总线发出LOCK#信号,此时其他处理器的对该变量的操作请求将被阻塞,发出锁定信号的处理器将独占共享内存,于是更新就是原子性的了. 1.compareAndSet- ...
- 死磕 java原子类之终结篇(面试题)
概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...
- Java 原子类 java.util.concurrent.atomic
Java 原子类 java.util.concurrent.atomic 1.i++为什么是非线程安全的 i++其实是分为3个步骤:获取i的值, 把i+1, 把i+1的结果赋给i 如果多线程执行i++ ...
- Java原子类实现原理分析
在谈谈java中的volatile一文中,我们提到过并发包中的原子类可以解决类似num++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...
随机推荐
- HDU4287
Intelligent IME Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)T ...
- canvas-arc.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 微信小程序地图报错——ret is not defined
刚刚在使用微信的map做地图时候 发现如下报错: 后来找了一会发现错误为经纬度写反了导致经纬度超出了范围 正确取值范围: latitude 纬度 浮点数,范围 -90 ~ 90 longitud ...
- loadrunner 运行场景-常见Graph简介
运行场景-常见Graph简介 by:授客 QQ:1033553122 A. Web Resource Graphs 1. 概述 a) Hits per Second Graph Hits ...
- MVC与单元测试实践之健身网站(七)-添加计划
计划的制定涉及到周期-动作包-动作的关联操作,在上一篇<计划的添加与重置>完成了周期的设置.动作包的添加,现在要完成的是动作的添加操作. 一 具体功能 a) 在选定了一个大周期具有的天数 ...
- 程序员简单打造一个灵活智能的自动化运维系统C#实例程序
你是一个程序员,被派去管理公司500台计算机.这些机器可能需要执行一些自动化任务,一台台手动操作会把你累死.重复性的工作还是交给电脑处理,怎么解决这个问题呢?一个自动化的运维系统是必须的.自己实现的好 ...
- 【详细】【转】C#中理解委托和事件
文章是很基础,但很实用,看了这篇文章,让我一下回到了2016年刚刚学委托的时候,故转之! 1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托是C#中类型安全的,可以订阅一个或多个具 ...
- Spark线性回归实现优化
import org.apache.log4j.{Level, Logger} import org.apache.spark.ml.feature.VectorAssembler import or ...
- 将mssql数据库高版本迁移到低版本
将mssql数据库高版本迁移到低版本 在低版本目标数据库中创建目标空数据库[TargetDb] ,注意新建数据库即可,不要创建任何表 在低版本数据库中,选中[服务器对象=>链接服务器] 右键[新 ...
- 【PAT】A1001A+B Format
最新想法: 最多是七位数,而且只有一组输入,完全不用考虑算法复杂度. 直接判断是否为负,并输出符号 巧妙的地方:while循环的下一次再添加逗号.(防止出现,999,991的情况) 婼姐的方法真的很巧 ...