文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

聊一聊Java中的原子类

2024-12-03 15:22

关注

在前面的内容中,我们已经学习了CAS的原理,所以对于学习本节来说会非常容易。本节介绍Java中的原子类是java.util.concurrent.atomic包下的对象,他们之所以有原子性的共性,都来源于CAS,可见CAS的重要性。对于原子类变量的操作是不会存在并发性问题的,不需要使用同步手段进行并发控制。它底层自身的实现即可保证变量的可见性以及操作的原子性,一般我们可以使用AtomicInteger,AtomicLong等实现计数器等功能,利用AtomicBoolean实现标志位等功能。

原子类是JDK5提供的,当时只有12个原子类,发展到JDK8时,又多出了4个原子类,如下图2-25所示,红色框内为JDK8新增加的。

 

图2-25 Java16个原子类

下面我们来对这些原子类进行分类讲解。

2.10.1原子更新基本类型

l AtomicBoolean: 原子更新布尔类型。

l AtomicInteger: 原子更新整型。

l AtomicLong: 原子更新长整型。

我们以AtomicInteger为例,AtomicIngeter的常用方法如下:

n int addAndGet(int delta): 以原子的方式将参数与实例中的值相加,并返回结果。

n boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值。

n int getAndIncrement(): 以原子的方式将当前值加1,然后返回自增前的值,也就是旧值。此方法也是比较常用的方法,可以用来做计数器。

n void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

n int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值。

n int incrementAndGet(): 和getAndIncrement一样,他返回的是自增后的值。

记得在讲解CAS应用的代码案例中,使用过原子自增的方法,下面我们看看getAndIncrement() 是如何实现原子操作的,请看2-45示例代码中AtomicInteger部分源码。

代码清单2-45 AtomicInteger.java

  1. public final int getAndIncrement() { 
  2.         return unsafe.getAndAddInt(this, valueOffset, 1); 
  3.     } 
  4.  
  5. public final int getAndAddInt(Object var1, long var2, int var4) { 
  6.         int var5; 
  7.         do { 
  8.             var5 = this.getIntVolatile(var1, var2); 
  9.         } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 
  10.  
  11.         return var5; 
  12.     } 

我们取得了旧值,然后把要加的数传过去,调用getAndAddInt () 进行原子更新操作,实际最核心的方法是 compareAndSwapInt(),使用CAS进行更新。我们Unsafe只提供了3中CAS操作,另外注意,AtomicBoolean 是把Boolean转成整型,在使用 compareAndSwapInt 进行操作的。在atomic包里的对象基本都是使用Unsafe提供的3中CAS操作的方法实现的,请看Unsafe源码,如代码清单2-46所示。

代码清单2-46 Unsafe.java

  1.  
  2. public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); 
  3.  
  4.     public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); 
  5.  
  6.     public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); 

2.10.2原子更新数组

l AtomicIntegerArray: 原子更新整型数组里的元素。

l AtomicLongArray: 原子更新长整型数组里的元素。

l AtomicReferenceArray: 原子更新引用类型数组里的元素。

这三个类的最常用的方法是如下两个方法:

n get(int index):获取索引为index的元素值。

n compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置为update值。

2.10.3原子更新引用类型

l AtomicReference: 原子更新引用类型。

l AtomicReferenceFieldUpdater: 原子更新引用类型的字段。

l AtomicMarkableReferce: 原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型。

这三个类提供的方法都差不多,首先构造一个引用对象,然后把引用对象set进Atomic类,然后调用compareAndSet等一些方法去进行原子操作,原理都是基于Unsafe实现,但AtomicReferenceFieldUpdater略有不同,更新的字段必须用volatile修饰。下面我们使用原子引用类型写一个简单的Demo,请看示例代码2-47所示

代码清单2-47 AtomicReferenceDemo.java

  1. public class AtomicReferenceDemo { 
  2.     public static AtomicReference<User> ai = new AtomicReference<User>(); 
  3.  
  4.     public static void main(String[] args) { 
  5.  
  6.         User u1 = new User("pangHu", 18); 
  7.         ai.set(u1); 
  8.         User u2 = new User("pangPang", 15); 
  9.         ai.compareAndSet(u1, u2); 
  10.         System.out.println(ai.get().getAge() + ai.get().getName()); 
  11.     } 
  12.  
  13.     static class User { 
  14.         private String name
  15.         private int age; 
  16.  //省略getter、settrt 
  17.     } 

输出结果。

15pangPang

2.10.4原子更新字段类

如果需要原子的更新类里某个字段时,需要用到原子更新字段类,Atomic包提供了3个类进行原子字段更新:

l AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。

l AtomicLongFieldUpdater: 原子更新长整型字段的更新器。

l AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。该方法比较重要,他和引用类型加上一个整数值,可以控制数据的版本号,这样就可以解决CAS更新时可能出现的ABA问题。和引用类型一样更新类的字段必须使用 public volatile 修饰。

2.10.5 JDK8新增原子类简介

l DoubleAccumulator

l LongAccumulator

l DoubleAdder

l LongAdder

下面以 LongAdder 为例介绍一下,并列出使用注意事项。

这些类对应把 AtomicLong 等类的改进。比如 LongAccumulator 与 LongAdder 在高并发环境下比 AtomicLong 更高效。

Atomic、Adder在低并发环境下,两者性能很相似。但在高并发环境下,Adder 有着明显更高的吞吐量,但是有着更高的空间复杂度。

LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator。

sum()方法在没有并发的情况下调用,如果在并发情况下使用会存在计数不准,下面有代码为例。

LongAdder不可以代替AtomicLong,虽然 LongAdder的add()方法可以原子性操作,但是并没有使用 Unsafe 的CAS算法,只是使用了CAS的思想。

 

LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator,LongAccumulator提供了比LongAdder更强大的功能,构造函数其中accumulatorFunction一个双目运算器接口,根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。

本文转载自微信公众号「晏霖」,可以通过以下二维码关注。转载本文请联系晏霖公众号。

 

来源:晏霖内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯