原子操作类简介

由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。 实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去 更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。 atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在Java中则是使用CAS操作具体实现。

CAS

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。 这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。 硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。 CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。 当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

//著名的CAS
//var1是比较值所属的对象,var2需要比较的值(但实际是使用地址偏移量来实现的),
//如果var1对象中偏移量为var2处的值等于var4,那么将该处的值设置为var5并返回true,如果不等于var4则返回false。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);复制代码

  • CAS的问题

1.ABA问题

如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。

J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题, 它可以通过控制变量值的版本来保证 CAS 的正确性。 大部分情况下 ABA 问题不会影响程序并发的正确性, 如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。

2.自旋时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用, 第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源, 延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。 第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation) 而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3.只能保证一个共享变量的原子操作 CAS只对单个共享变量有效,当操作涉及跨多个共享变量时CAS无效。 但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性, 可以把多个变量封装成对象里来进行 CAS 操作. 所以我们可以使用锁或者利用AtomicReference类把多个共享变量封装成一个共享变量来操作。

  • synchronized VS CAS

元老级的synchronized(未优化前)最主要的问题是: 在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。 而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而不是进行耗时的挂起唤醒的操作, 因此也叫做非阻塞同步。这是两者主要的区别。

原子更新基本类

atomic包提高原子更新基本类的工具类,如下:

AtomicBoolean //以原子更新的方式更新BooleanAtomicIntege //以原子更新的方式更新IntegerAtomicLong //以原子更新的方式更新Long复制代码

  • 以AtomicInteger为例总结常用的方法:
addAndGet(int delta) //以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果incrementAndGet() //以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果getAndSet(int newValue) //将实例中的值更新为新值,并返回旧值getAndIncrement() //以原子的方式将实例中的原值加1,返回的是自增前的旧值复制代码

AtomicInteger的getAndIncrement()方法源码如下:

public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}复制代码

实际上是调用了unsafe实例的getAndAddInt方法,unsafe实例的获取时通过UnSafe类的静态方法getUnsafe获取:

private static final Unsafe unsafe = Unsafe.getUnsafe();
public class AtomicIntegerDemo {// 请求总数public static int clientTotal = 5000;// 同时并发执行的线程数public static int threadTotal = 200;//java.util.concurrent.atomic.AtomicInteger;public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();//Semaphore和CountDownLatch模拟并发final Semaphore semaphore = new Semaphore(threadTotal);final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal ; i++) {executorService.execute(() -> {try {semaphore.acquire();add();semaphore.release();} catch (Exception e) {e.printStackTrace();}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("count:{"+count.get()+"}");}public static void add() {count.incrementAndGet();}
}复制代码

输出结果:

count:{5000}复制代码

AtomicLong的实现原理和AtomicInteger一致,只不过一个针对的是long变量,一个针对的是int变量。 而boolean变量的更新类AtomicBoolean类是怎样实现更新的呢?核心方法是compareAndSet()方法,其源码如下:

public final boolean compareAndSet(boolean expect, boolean update) {int e = expect ? 1 : 0;int u = update ? 1 : 0;return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}复制代码

可以看出,compareAndSet方法的实际上也是先转换成0,1的整型变量, 然后是通过针对int型变量的原子更新方法compareAndSwapInt来实现的。 可以看出atomic包中只提供了对boolean,int ,long这三种基本类型的原子更新的方法, 参考对boolean更新的方式,原子更新char,doule,float也可以采用类似的思路进行实现。

原子更新数组

atomic包下提供能原子更新数组中元素的类有:

AtomicIntegerArray //原子更新整型数组中的元素AtomicLongArray //原子更新长整型数组中的元素AtomicReferenceArray //原子更新引用类型数组中的元素复制代码

这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

getAndAdd(int i, int delta) //以原子更新的方式将数组中索引为i的元素与输入值相加getAndIncrement(int i) //以原子更新的方式将数组中索引为i的元素自增加1compareAndSet(int i, int expect, int update) //将数组中索引为i的位置的元素进行更新复制代码

可以看出,AtomicIntegerArray与AtomicInteger的方法基本一致, 只不过在AtomicIntegerArray的方法中会多一个指定数组索引位i。

public class AtomicIntegerArrayDemo {private static int[] value = new int[]{1, 2, 3};private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);public static void main(String[] args) {//对数组中索引为1的位置的元素加5int result = integerArray.getAndAdd(1, 5);System.out.println(integerArray.get(1));System.out.println(result);}
}复制代码

输出结果:

7
2复制代码

原子更新引用类型

如果需要原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:

AtomicReference //原子更新引用类型AtomicReferenceFieldUpdater //原子更新引用类型里的字段AtomicMarkableReference //原子更新带有标记位的引用类型复制代码

这几个类的使用方法也是基本一样的,以AtomicReference为例。

public class AtomicReferenceDemo {private static AtomicReference<User> reference = new AtomicReference<>();public static void main(String[] args) {User user1 = new User("a", 1);reference.set(user1);User user2 = new User("b",2);User user = reference.getAndSet(user2);System.out.println(user);System.out.println(reference.get());}static class User {private String userName;private int age;public User(String userName, int age) {this.userName = userName;this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}}
}复制代码

输出结果:

User{userName='a', age=1}
User{userName='b', age=2}复制代码

首先将对象User1用AtomicReference进行封装,然后调用getAndSet方法, 从结果可以看出,该方法会原子更新引用的user对象, 变为User{userName='b', age=2},返回的是原来的user对象User{userName='a', age=1}。

原子更新字段类型

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:

AtomicIntegeFieldUpdater //原子更新整型字段类AtomicLongFieldUpdater //原子更新长整型字段类AtomicStampedReference //原子更新引用类型,这种更新方式会带有版本号。
// 而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题;复制代码

要想使用原子更新字段需要两步操作:

  • 原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性
  • 更新类的属性必须使用public volatile进行修饰

这几个类提供的方法基本一致,以AtomicIntegerFieldUpdater为例。

public class AtomicIntegerFieldUpdaterDemo {private static AtomicIntegerFieldUpdater updater =AtomicIntegerFieldUpdater.newUpdater(User.class,"age");public static void main(String[] args) {User user = new User("a", 1);int oldValue = updater.getAndAdd(user, 5);System.out.println(oldValue);System.out.println(updater.get(user));}static class User {private String userName;public volatile int age;public User(String userName, int age) {this.userName = userName;this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}}
}复制代码

输出结果:

1
6复制代码

创建AtomicIntegerFieldUpdater是通过它提供的静态方法进行创建, getAndAdd方法会将指定的字段加上输入的值,并且返回相加之前的值。 user对象中age字段原值为1,加5之后,可以看出user对象中的age字段的值已经变成了6。

转载于:https://juejin.im/post/5cece751e51d454fd8057b16

Java原子操作类,你知道多少?相关推荐

  1. Java原子操作类AtomicInteger应用场景

    参考文章:Java原子操作类AtomicInteger应用场景 感谢作者分享!

  2. Java原子操作类,知多少?

    前文我们介绍了Java并发编程中的两个关键字:volatile和synchronized.我们也知道了volatile虽然是轻量级,但不能保证原子性,synchronized可以保证原子性,但是比较重 ...

  3. Java高并发编程(九):Java中原子操作类

    Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量的方式. ...

  4. Java Review - 并发编程_原子操作类LongAdder LongAccumulator剖析

    文章目录 概述 小Demo 源码分析 重要的方法 long sum() reset sumThenReset longValue() add(long x) longAccumulate(long x ...

  5. Java Review - 并发编程_原子操作类原理剖析

    文章目录 概述 原子变量操作类 主要方法 incrementAndGet .decrementAndGet .getAndIncrement.getAndDecrement boolean compa ...

  6. Java中12个原子操作类

    Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包(以下简称Atomic包),这个包中的 原子操作类 提供了一种用法简单.性能高效.线程安全地更新一个 ...

  7. java中的原子操作_[Java并发系列] 2.Java中的原子操作类

    1. 原子操作类的作用 当程序更新一个变量时,如果多个线程同时更新该变量,可能会得到期望以外的值.比如i=1, 线程A更新i+1, 同时线程B更新I+1,经过两个线程的操作,最终变量i的值可能不是3, ...

  8. CAS、原子操作类的应用与浅析及Java8对其的优化

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:CoderBear juejin.im/post/5c7a8 ...

  9. 原子操作类AtomicInteger详解

    为什么需要AtomicInteger原子操作类? 对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的.num++解析为num=num+1,明显,这个操作 ...

最新文章

  1. 你所了解的python岗位有哪些_Python是最火语言之一,那么他适合做哪些岗位?Python最全岗位!...
  2. Eureka深入理解
  3. C#InI文件读写剖析
  4. Java中实现统计一个字符串在另一个字符串中出现的次数统计
  5. js 浅拷贝直接赋值_浅析JavaScript解析赋值、浅拷贝和深拷贝的区别
  6. matlab如何测两点的角度_【邢不行|量化小讲堂系列01-Python量化入门】如何快速上手使用Python进行金融数据分析...
  7. 录制怎么没有耳机设备_录制现场演示,第1部分:选择设备
  8. 【踩坑】Linux下配置torch-geometric
  9. java-redis字符类数据操作示例(一)
  10. 支持HTML5的浏览器
  11. linux磁带机找不到,如何在Linux下安装磁带机
  12. 百度初级认证考试知识点
  13. 六、矩阵键盘的扫描原理与基本应用
  14. java计算机毕业设计猎头公司业务管理系统的设计与实现源代码+数据库+系统+lw文档
  15. 小白Nvidia TK1 Jetpack安装/重装系统详细步骤(小车第一步)
  16. 使用SVG.Net生成svg格式文字图片
  17. lly的数列询问(最小生成树 + 思维)
  18. R语言——ggplot2的绘图逻辑
  19. 说说你对keep-alive的理解是什么?
  20. BUPT-CSAPP 2019 Fall 3.58 3.60 3.63

热门文章

  1. 腾讯技术工程 | 腾讯数据平台部总监刘煜宏:这5大产品平台,展示了腾讯大数据的核心能力...
  2. spring aop xml事务配置
  3. 多迪新手学UI设计,拿高薪必须掌握的几个小技巧?
  4. 机器学习算法一览,应用建议与解决思路
  5. 经常使用的eclipse插件
  6. [C# 网络编程系列]专题四:自定义Web浏览器
  7. IPsec在企业网中的应用
  8. 将 Silverlight 与网页集成【转载】
  9. 【Runtime Error】打开Matlib7.0运行程序报错的解决办法
  10. 切糕[HNOI2013]