网站建设用哪个好,在线制图网,公司策划是做什么的,湘潭做网站价格找磐石网络一流基于JDK1.8详细介绍了JUC下面的atomic子包中的大部分原子类的底层源码实现#xff0c;比如AtomicInteger、AtomicIntegerArray、AtomicStampedReference等原子类源码。最后还介绍了JDK1.8对原子类的增强#xff0c;比如LongAdder和LongAccumulator的原理#xff01; 文章目录… 基于JDK1.8详细介绍了JUC下面的atomic子包中的大部分原子类的底层源码实现比如AtomicInteger、AtomicIntegerArray、AtomicStampedReference等原子类源码。最后还介绍了JDK1.8对原子类的增强比如LongAdder和LongAccumulator的原理 文章目录 1 atomic的概述2 原子更新单个变量2.1 基本原子类2.2 带版本号的原子类 3 原子更新数组3.1 重要属性3.2 重要方法 4 原子更新字段属性5 原子类的加强6 atomic的总结 1 atomic的概述
JDK1.5之前为了保证Java中对单个变量的多个独立操作的原子性和安全性通常会使用到synchronized锁但是synchronized需要底层操作系统mutex资源的支持这是一种重量级资源性能比较低
JDK1.5的时候新增了JUC包增加了许多和同步有关的特性大大提高了使用Java进行并发编程的效率比如并发集合、并发队列、新lock锁等。另外JUC包下面还提供了一个java.util.concurrent.atomic子包这个atomic包中的类用于在多线程环境下实现单个变量多个独立操作比如读-写的连续原子性并且都比较高效因为它们都是由基于偏移量类似于指针的非阻塞CAS算法实现用于替代锁的使用。
JDK1.8的atomic包中具有17个原子类根据支持的更新变量的类型我们可以对常用原子类分为三种分别是原子更新单个变量、原子更新数组、原子更新引用属性字段。
atomic 包下的常用原子类如下
类摘要AtomicBoolean用原子方式更新的 boolean 值。AtomicInteger用原子方式更新的 int 值。AtomicLong用原子方式更新的 long 值。AtomicReference V 用原子方式更新的对象引用。AtomicMarkableReference V 维护带有boolean标志位的对象引用可以原子方式对其进行更新。AtomicStampedReference V 维护带有int整数版本号的对象引用可用原子方式对其进行更新。AtomicIntegerArray用原子方式更新其元素的 int 数组。AtomicLongArray用原子方式更新其元素的 long 数组。AtomicReferenceArray E 用原子方式更新其元素的对象引用数组。AtomicIntegerFieldUpdater T 基于反射的实用工具可以对指定类的指定非私有非静态的 volatile int 字段进行原子更新。AtomicLongFieldUpdater T 基于反射的实用工具可以对指定类的指定非私有非静态的 volatile long 字段进行原子更新。AtomicReferenceFieldUpdater T,V 基于反射的实用工具可以对指定类的指定非私有非静态的 volatile 引用字段进行原子更新 。LongAdderJDK1.8新增加的原子类累加器使用热点数据分离的思想对long数据进行加法运算性能更佳LongAccumulatorJDK1.8新增加的原子类累加器使用热点数据分离的思想对long数据进行指定规则的运算性能更佳DoubleAdderJDK1.8新增加的原子类累加器使用热点数据分离的思想对double数据进行加法运算性能更佳DoubleAccumulatorJDK1.8新增加的原子类累加器使用热点数据分离的思想对double数据进行指定规则的运算性能更佳
实际上Java中atomic包下的原子类的基石就是volatile字段修饰符CAS算法Unsafe提供。本文没有对这两个基本知识点做深入讲解因为前面的文章中已经讲了都是深入到了虚拟机源码级别如果想要深入了解原子类的原理应该要看看以下文章Java中的volatile实现原理深度解析以及应用和Java中的CAS实现原理深度解析与应用案例。
2 原子更新单个变量
2.1 基本原子类
通过原子的方式更新单个变量Atomic包提供了以下4个基础类
1. AtomicBoolean用原子方式更新的 boolean 值。
2. AtomicInteger用原子方式更新的 int 值。
3. AtomicLong用原子方式更新的 long 值。
4. AtomicReference V 用原子方式更新的对象引用。上面四个原子类的原理几乎一致我们以AtomicInteger来讲解。
AtomicInteger源码解析Java AtomicInteger和AtomicStampedReference源码深度解析。
2.2 带版本号的原子类
通过原子的方式更新单个变量的原子类的升级版Atomic包提供了以下2个类
1. AtomicMarkableReference V 维护带有标记位的对象引用可以原子方式对其进行更新。
2. AtomicStampedReference V 维护带有整数标志的对象引用可用原子方式对其进行更新。上面两个原子类的方法以及原理几乎一致属于带有版本号的原子类。我们知道CAS操作的三大问题之一就是“ABA”问题CAS在操作值的时候需要检查预期值有没有发生变化如果没有发生变化则更新。但是如果一个线程t1首先获取了预期值A此时另一个线程t2则将值从A变成了B随后又变成了A随后t1再使用CAS进行比较交换的时候会发现它的预期值“没有变化”但实际上是变化过的。这就是ABA问题的由来。
ABA问题的解决思路就是使用版本号1A-2B-3A在Atomic包中提供了一个现成的AtomicStampedReference类来解决ABA问题使用的就是添加版本号的方法。还有一个AtomicMarkableReference实现类它比AtomicStampedReference更加简单AtomicStampedReference中每更新一次数据版本号也会更新一次这样可以使用版本号统计到底更新了多少次而AtomicMarkableReference仅仅使用了一个boolean值来表示值是否改变过因此使用的比较少。
这里我们以AtomicStampedReference来讲解。
AtomicStampedReference源码解析Java AtomicInteger和AtomicStampedReference源码深度解析。
3 原子更新数组
通过原子的方式更新数组里的某个元素Atomic包提供了以下3个类
1. AtomicIntegerArray用原子方式更新其元素的 int 数组。
2. AtomicLongArray用原子方式更新其元素的 long 数组。
3. AtomicReferenceArray E 用原子方式更新其元素的对象引用数组。上面三个原子类的原理几乎一致我们以AtomicIntegerArray来讲解。
3.1 重要属性
可以看到内部就是一个int的数组然后调用Unsafe的方法对数组的元素进行操作。
/*** 使用Unsafe操作数组*/
private static final Unsafe unsafe Unsafe.getUnsafe();
/*** 返回数组类型的第一个元素的偏移地址(基础偏移地址)。* 如果arrayIndexScale方法返回的比例因子不为0你可以通过结合基础偏移地址和比例因子访问数组的所有元素。*/
private static final int base unsafe.arrayBaseOffset(int[].class);
/*** scale最高位的1的所在位数(从左从0开始)在计算某个索引的偏移量的时候* 使用是该值进行位运算而不是scale进行传统乘法运算提升效率*/
private static final int shift;
/*** 底层int数组*/
private final int[] array;static {//返回数组单个元素的大小数组中的元素的地址是连续的64位虚拟机应该是4int scale unsafe.arrayIndexScale(int[].class);//大小必须是2的幂次方if ((scale (scale - 1)) ! 0)throw new Error(data type scale not a power of two);//numberOfLeadingZeros用于返回scale的最高非零位前面的0的个数包括符号位在内//31减去scale的最高非零位前面的0的个数就表示scale最高位的1的所在位数比如scale为2那么shift为1如果scale为4那么shift为2shift 31 - Integer.numberOfLeadingZeros(scale);
}/*** 某个数组索引位置的元素的偏移量** param i 数组索引* return 该索引的偏移量*/
private long checkedByteOffset(int i) {if (i 0 || i array.length)throw new IndexOutOfBoundsException(index i);return byteOffset(i);
}/*** param i 索引位置* return 返回某个数组索引位置的元素的偏移量*/
private static long byteOffset(int i) {//这里就能明白shift的作用了对于2的幂次方的scale//这里可以使用scale的最高为1的位置shift的位运算i shift代替scale*i的传统运算效率提高//比如scale4那么shift2如果i3那么ishift 3 2 12 就等于 scale*i 4 * 3 12//比如scale8那么shift3如果i3那么ishift 3 3 24 就等于 scale*i 8 * 3 24return ((long) i shift) base;
}/*** 创建给定长度的新 AtomicIntegerArray。** param length 给定长度*/
public AtomicIntegerArray(int length) {array new int[length];
}/*** 创建与给定数组具有相同长度的新 AtomicIntegerArray并从给定数组复制其所有元素。** param array 给定数组* throws NullPointerException 如果数组为 null*/
public AtomicIntegerArray(int[] array) {// 克隆数组元素浅克隆this.array array.clone();
}3.2 重要方法
其常用方法如下基于Unsafe的volatile和CAS操作
/*** 获取i索引位置的当前值** param i 多赢* return 当前值*/
public final int get(int i) {return getRaw(checkedByteOffset(i));
}private int getRaw(long offset) {//volatile的获取最新值return unsafe.getIntVolatile(array, offset);
}/*** 在i索引位置设定为指定新值** param i 索引* param newValue 新值*/
public final void set(int i, int newValue) {//volatile的写unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}/*** 以原子方式将元素设置在i索引位置并返回旧值** param i 索引* param newValue 新值* return 旧值*/
public final int getAndSet(int i, int newValue) {return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}/*** 以原子方式将输入值与数组中索引i的元素相加并返回旧值** param i 索引* param delta 相加的数据* return 旧值*/
public final int getAndAdd(int i, int delta) {return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}/*** 以原子方式将输入值与数组中索引i的元素相加并返回新值** param i 索引* param delta 相加的数据* return 更新后的值*/
public final int addAndGet(int i, int delta) {return getAndAdd(i, delta) delta;
}/**1. 如果当前值等于预期值则以原子方式将数组位置i的元素设置成新值。2. 3. param i 索引3. param expect 预期值4. param update 新值5. return true表示CAS成功 false 表示CAS失败*/
public final boolean compareAndSet(int i, int expect, int update) {return compareAndSetRaw(checkedByteOffset(i), expect, update);
}4 原子更新字段属性
通过原子的方式更新对象里的某个字段Atomic包提供了以下3个类
1. AtomicIntegerFieldUpdater T 基于反射的实用工具可以对指定类的指定非私有的 volatile int字段进行原子更新。
2. AtomicLongFieldUpdater T 基于反射的实用工具可以对指定类的指定非私有的 volatile long字段进行原子更新。
3. AtomicReferenceFieldUpdater T,V 基于反射的实用工具可以对指定类的指定非私有的 volatile引用字段进行原子更新。以上3个类的原理几乎一样我们以AtomicIntegerFieldUpdater来讲解。
AtomicIntegerFieldUpdater实际上是一个抽象类它的实现类实际上在它的内部而且是私有的因此只能使用静态方法newUpdater()创建一个更新器并且需要设置想要更新的类和属性字符串名。
另外这里对于对象的字段的设置是先采用getDeclaredField方法反射获取的对应字段的Filed对象然后在对Filed对象进行操作并且没有设置setAccessible权限因此类的字段属性不能是私有属性
由于CAS 操作会通过对象实例中的偏移量直接进行赋值即Unsafe. objectFieldOffset()方法。因此它不支持对static属性的赋值。
对象的字段还应该被设置为volatile类型这样就能获取到最新的值。
/**1. author lx*/
public class AtomicFieldUpdaterTest {public static void main(String[] args) {AtomicIntegerFieldUpdaterUser old AtomicIntegerFieldUpdater.newUpdater(User.class, old);AtomicReferenceFieldUpdaterUser, String name AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, name);User user new User(user, 10);System.out.println(old.getAndIncrement(user));System.out.println(old.get(user));System.out.println(name.getAndSet(user, user2));System.out.println(name.get(user));}public static class User {volatile String name;volatile int old;User(String name, int old) {this.name name;this.old old;}public String getName() {return name;}public int getOld() {return old;}}
}5 原子类的加强
JDK1.8的时候新增了四个原子类
1. LongAdderlong类型的数值累加器从0开始累加累加规则为加法运算。
2. LongAccumulatorlong类型的数值累加器可从指定值开始累加可指定累加规则。
3. DoubleAdderdouble类型的数值累加器从0开始累加累加规则为加法运算。
4. DoubleAccumulatordouble类型的数值累加器可从指定值开始累加可指定累加规则。自从原子类问世之后多线程环境下如果用于统计计数操作一般可以使用AtomicLong来代替锁作为计数器AtomicLong 通过CAS 提供了非阻塞的原子性操作相比使用阻塞算法的同步器来说它的性能己经很好了那么它们有什么缺点吗
实际上AtomicLong等其他传统的atomic原子类对于数值的更改通常都是在一个无限循环自旋中不断尝试CAS 的修改操作一旦CAS失败则循环重试这样来保证最终CAS操作成功。如果竞争不激烈那么修改成功的概率就很高但是如果在高并发下大量线程频繁的竞争修改计数器会造成一次CAS修改失败的概率就很高。在大量修改失败时这些原子操作就会进行多次循环尝试白白浪费CPU 资源因此性能还是会受到影响。
JDK1.8新增这些类正是为了解决高并发环境下由于频繁读写AtomicLong等计数器而可能造成某些线程持续的空转循环进而浪费CPU的情况它们也被称为“累加器”
LongAdder和DoubleAdderLongAccumulator和DoubleAccumulator的原理差不多。实际上DoubleAdder中对于double的累加也是先通过Double.doubleToRawLongBits将double类型转换为long类型来进行计算的并且底层也是存储的long类型的值在获取总和的时候又会通过Double.longBitsToDouble将存储的long值转换为double。
下面我们将对LongAdder和LongAccumulator进行讲解
LongAdderJava LongAdder原子加法器源码深度解析。
LongAccumulatorJava LongAccumulator原子累加器源码深度解析。
6 atomic的总结
JDK1.5出现的atomic包下面的原子类在对于单个变量的复合操作比如读-写中可以代替锁的来保证操作的原子性和安全性并且由于没有使用锁而有不错的性能但是对于多个变量的复合操作以及一批代码的原子性和安全性却无能为力此时只能使用锁。
我们可以看到实际上volatile关键字以及Unsafe类提供的CAS的方法就是构成原子类的基石原子类的方法实际上就是对于Unsafe中的CAS方法的二次包装方法开发人员使用而已。Unsafe中的CAS方法作为native方法本身并不是Java语言实现的它们的源码位于JVM虚拟机的源码中HotSpot虚拟机的源码中就有这些native方法的具体实现它们都是采用C的代码实现的方便与底层系统交互在openjdk中可以找到。
本文没有对一些基本知识点做深入讲解比如Unsafe、volatile、CAS、伪共享、JMH等因为前面的文章中已经讲了都是深入到了虚拟机源码级别如果想要深入了解原子类的原理应该要看看以下文章
相关文章
UnsafeJUC—Unsafe类的原理详解与使用案例。volatileJava中的volatile实现原理深度解析以及应用。CASJava中的CAS实现原理深度解析与应用案例。伪共享Java中的伪共享深度解析以及避免方法。JMHJava使用JMH进行方法性能优化测试。 如果有什么不懂或者需要交流可以留言。另外希望点赞、收藏、关注我将不间断更新各种Java学习博客