对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
当一个处理器想要更新某个变量的值时,向总线发出LOCK#信号,此时其他处理器的对该变量的操作请求将被阻塞,发出锁定信号的处理器将独占共享内存,于是更新就是原子性的了. 1.compareAndSet- ...
- Java原子类中CAS的底层实现
Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...
- 死磕 java原子类之终结篇(面试题)
概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...
- 源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)
一.前言 前一阵子比较好奇,想看到底层(虚拟机.汇编)怎么实现的java 并发那块. volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看. 但是原子 ...
- 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++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...
随机推荐
- python数据分析panda库
panda内有两种数据结构,Series()和DataFrame() >>> a=pd.Series([1,2],index=['a','b']) >>> a a ...
- eclipse安装java ee插件方法步骤
1.本人以前使用的MyEclipse进行Javaweb开发,但是后来由于myeclipse实在太臃肿,经常在运行的过程中不流畅 (可能电脑内存也不是太高吧) !所以坚决换用eclipse,但是问题 ...
- java 基础语法 2
一.语句
- Visual Studio + Qt开发环境搭建
1. 安装Visual Studio 2015 Visual Studio 2015下载地址如下,安装比较常规,不做介绍. Visual Studio Enterprise 2015 with Upd ...
- Java并发之线程中断
前面的几篇文章主要介绍了线程的一些最基本的概念,包括线程的间的冲突及其解决办法,以及线程间的协作机制.本篇主要来学习下Java中对线程中断机制的实现.在我们的程序中经常会有一些不达到目的不会退出的线程 ...
- HSF服务的开发与使用
1.HSF服务的开发 1) 基于Maven创建一个web工程HSFService,如下图,其他的可以自定义. 2)创建好好在src/main目录下创建一个java目录,并将其设置为sources fo ...
- 为什么说程序员都应该玩一玩GitHub
既熟悉又陌生的GitHub 关于GitHub,相信每一个程序员都再熟悉不过了.它为开发者提供Git仓库的托管服务,是全世界最大的代码集中地,被戏称为“全球最大同性交友网站”. 但是对于很大一部分程序员 ...
- sql的存储过程使用详解--基本语法
存储过程简介 SQL语句需要先编译然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储 ...
- C# 使用FileUpload控件上传图片,将文件转换成二进制进行存储与读取
状况描述: 需要上传文件,但是不想要保存到实体路径下,便可以用该功能来实现. 效果图: 点击[Upload]按钮,上传文件到数据库: 点击[Preview],预览文件: 具体实现: 前台: <t ...
- html5获取用户当前的地理位置,即经纬度。
$("document").ready(function(){ getMap(); }); function getMap(){ // 百度地图API功能 var map = ne ...