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实现原理的一点总结的更多相关文章

  1. 对Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  2. Java原子类及内部原理

    一.引入 原子是世界上的最小单位,具有不可分割性.比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作.再比如:a++: 这个操作实际是a = a + ...

  3. java的原子类 AtomicInteger 实现原理是什么?

    采用硬件提供原子操作指令实现的,即CAS.每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全. CAS机制 CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换. ...

  4. Java原子类中CAS的底层实现

    Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...

  5. 源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)

    一.前言 前一阵子比较好奇,想看到底层(虚拟机.汇编)怎么实现的java 并发那块. volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看. 但是原子 ...

  6. java:原子类的CAS

    当一个处理器想要更新某个变量的值时,向总线发出LOCK#信号,此时其他处理器的对该变量的操作请求将被阻塞,发出锁定信号的处理器将独占共享内存,于是更新就是原子性的了. 1.compareAndSet- ...

  7. 死磕 java原子类之终结篇(面试题)

    概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...

  8. Java 原子类 java.util.concurrent.atomic

    Java 原子类 java.util.concurrent.atomic 1.i++为什么是非线程安全的 i++其实是分为3个步骤:获取i的值, 把i+1, 把i+1的结果赋给i 如果多线程执行i++ ...

  9. Java原子类实现原理分析

    在谈谈java中的volatile一文中,我们提到过并发包中的原子类可以解决类似num++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...

随机推荐

  1. php7.27: export excel from mysql

    https://stackoverflow.com/questions/15699301/export-mysql-data-to-excel-in-php https://github.com/PH ...

  2. JS 数组位置方法 indexOf()和lastIndexOf()的理解

    var numbers = [1,3,5,7,9,4,3,2,1]; console.log(numbers.indexOf(5)); //从数组的0位开始查找 5 位于数组里面的位置 输出2 首先 ...

  3. 【读书笔记】iOS-移动开发

    一,iPhone 为iPhone编写基于Web的应用程序非常简单.Safari Web浏览器是一款很优秀的工具-它能够完美地对基于Web的应用程序进行缩放,以便在iPhone大小的屏幕上运行.Safa ...

  4. sqlserver配置实践

    对于一套新的sqlserver服务器,我们首先要对它做一些必要的优化配置,确保在生产上比较长的时间段内可以比较稳定的,良好的运行. 新的sqlserver服务器上安装的sqlserver版本,可以选择 ...

  5. Spooling Directory Source使用技巧

    1.使用文件原来的名字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 a1.sources=r1   a1.sinks=k1   a1.sources.r1.ty ...

  6. ::before和::after伪元素的妙用

    场景: 假如有一天,你的在写一个前端项目,是关于一份点餐商家电话信息表,你啪塔啪塔地写完了,突然间项目经理跑过来找你,要求你在每一个商家的电话号码前都添加一个电话符号,来使得电话号码更直观和页面更美观 ...

  7. 第3章 Git使用人门

    [初识Github] 首先让我们大家一起喊一句“Hello Github”.YEAH!就是这样. Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理 ...

  8. February 1st, 2018 Week 5th Thursday

    The world is a fine place, and worth fighting for. 这世界是个好地方,值得为之奋斗. The world is beautiful, there ar ...

  9. January 09th, 2018 Week 02nd Tuesday

    Use the smile to change the world. Don't let the world change your smile. 用你的笑容去改变这个世界,别让这个世界改变了你的笑容 ...

  10. 【微信JSSDK】PHP版微信录音文件下载

    微信的录音文件上传到微信服务器上,只能保存三天. 因此需要做一个转存到自己服务器,或者七牛云的操作. 转存到自己服务器 调用微信JSSDK API 录音, 录音结束,上传到微信服务器,获取录音文件的 ...