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++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...
随机推荐
- JS实现页面分享代码share,不需要第三方接口
在开发一个页面的时候常常会有这么一个小功能,就是分享该页面中的信息. 常见的分享代码有百度分享,JiaThis分享插件(现在貌似不能用了),bshare分享插件等,我主要分享一下自定义分享代码,如下: ...
- WebGL学习笔记(一)
作者:朱金灿 来源:http://blog.csdn.net/clever101 (一)WebGL是什么? WebGL是一门在网页上显示三维图形的技术,你可以把它理解为把OpenGL从C/S端搬到了B ...
- Java并发编程(八)同步容器
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch) 一.为什么会出现同步容器? ...
- JavaScript大杂烩4 - 理解JavaScript对象的继承机制
JavaScript是单根的完全面向对象的语言 JavaScript是单根的面向对象语言,它只有单一的根Object,所有的其他对象都是直接或者间接的从Object对象继承.而在JavaScript的 ...
- SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法
最近项目转型使用SpringCloud框架下的微服务架构,各微服务之间使用Feign进行调用.期间,发现若被调用方法涉及到文件上传且仅存在单个文件时,一切正常,代码片段如下: @RequestMapp ...
- [20171120]理解v$session的state字段(11G).txt
[20171120]理解v$session的state字段(11G).txt --//https://blogs.oracle.com/database4cn/vsession-%e4%bd%a0%e ...
- 洗礼灵魂,修炼python(11)--python函数,模块
前面的章节你如果看懂了,基本算是入门了七八了,不过如果你以为python就这么点东西,你觉得很简单啊,那你就错了,真正的东西在后面,前面我说的几大核心其实也不是多么高深多么厉害的,那些东西是基础很常用 ...
- Linux Regulator Framework(2)_regulator driver
转自蜗窝科技:http://www.wowotech.net/pm_subsystem/regulator_driver.html 说实话,这篇好难懂啊... 1. 前言 本文从regulator d ...
- 给电脑插上无线网卡,变成路由器----Windows系统承载网络的使用
1. 以管理员身份运行命令提示符(PowerShell) 2. 启用并设定虚拟wifi网卡 netsh wlan set hostednetwork mode=allow ssid=wifi名称 ke ...
- 【PAT】B1076 Wifi密码(15 分)
注意接收字符时缓冲区的换行要接受掉 #include<stdio.h> int main() { int n; scanf("%d", &n); n *= 4; ...