基本介绍:

Atomic指一个操作不可中断,即使在多线程情况下,一个操作一旦开始,就不会被其他线程干扰。如果多线程中仅需要Atomic原子类解决的事情,就不需要synchronized重量级锁了。

原子类共四类:

  1. 基本类型:使用原子的方式更新基本类型
      a. AtomicInteger整形原子类
      b. AtomicLong长整型原子类
      c. AtomicBoolean布尔原子类

  2. 数组类型:使用原子的方式更新数组中某个元素
      a. AtomicIntegerArray:整形数组原子类
      b. AtomicLongArray:长整形数组原子类
      c. AtomicReferenceArray:引用类型数组原子类(即对应数组中存放的元素为对象形式)

  3. 引用类型:使用原子的方式更新某个对象
      a. AtomicReference:引用类型原子类
      b. AtomicStampedReference:AtomicReference的扩展版,增加了一个参数stamp标记,这里是为了解决了AtomicInteger和AtomicLong的操作会出现ABA问题。
      c. AtomicMarkableReference :与AtomicStampedReference差不多,只不过第二个参数不是用的int作为标志,而用boolean类型做标记,具体用法看后面讲解。

  4. 对象的属性修改类型:使用原子的方式更新某个类中某个字段
      a. AtomicIntegerFieldUpdater:原子更新整形字段的更新器
      b. AtomicLongFieldUpdater:原子更新长整形字段的更新器
      c. AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器


使用方法:

一、基本类型原子类

由于三种类的方法基本一样,下面就以AtomicInteger 为例:

public final int set() //设一个值
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果当前值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue) //最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

public class AtomicIntegerTest {public static void main(String[] args) {AtomicInteger a = new AtomicInteger(0);for (int i = 1; i < 5; i++) {a.getAndIncrement(); // a 自增,相当于 a ++}//获取当前a的值System.out.println("AtomicInteger a从0自增4次结果为:"+ a.get());System.out.println("AtomicInteger 当前a为:"+ a.getAndDecrement() + ",并自减一次"); //a --//获取当前a的值,并更新a为8System.out.println("AtomicInteger a当前值为:"+a.getAndSet(8)+",并更新a为8");//获取当前a的值,并将a加6System.out.println("AtomicInteger a当前值为:"+a.getAndAdd(6)+",并将a加6");a.compareAndSet(12,9); //如果a=12,就把a更新为9,否则不进行操作System.out.println("AtomicInteger a当前值为:"+a.get());a.compareAndSet(14,9); //如果a=14,就把a更新为9,否则不进行操作System.out.println("AtomicInteger a当前值为:"+a.get());}
}

二、数组类型原子类

由于三种类的方法基本一样,下面就以AtomicIntegerArray 为例:

public final int get(int i) //获取 index=i 位置元素的值
public final int set(int i, int newValue) //为 index=i 位置元素设新值
public final int getAndSet(int i, int newValue) //返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue) //最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

public class AtomicIntegerArrayTest {public static void main(String[] args) {int[] a = {1,1,1,1};AtomicIntegerArray arr = new AtomicIntegerArray(a);System.out.println("arr数组初始值为:" +arr.toString());for (int i = 0; i < 4; i++) {arr.getAndIncrement(i); //index = i位置上的值arr[i]自增,相当于 a[i] ++}System.out.println("arr数组每个元素都自增1后为:" +arr.toString());//获取当前arr[1]的值System.out.println("arr[1]的值为:"+ arr.get(1));System.out.println("arr[2]当前值为:"+ arr.getAndDecrement(2) + ",并让arr[2]自减一次"); //a[2]--//获取当前a[2]的值,并更新a为8System.out.println("arr[2]当前值为:"+arr.getAndSet(2,8)+",并更新a[2]为8");//获取当前a的值,并将a加6System.out.println("arr[2]当前值为:"+arr.getAndAdd(2,6)+",并将a[2]加6");arr.compareAndSet(2,12,9); //如果a[2]=12,就把a[2]更新为9,否则不进行操作System.out.println("arr[2]当前值为:"+arr.get(2));arr.compareAndSet(2,14,9); //如果a[2]=14,就把a[2]更新为9,否则不进行操作System.out.println("arr[2]当前值为:"+arr.get(2));}
}

三、引用原子类AtomicReference

public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(final String name) {this.name = name;}public int getAge() {return this.age;}public void setAge(final int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class AtomicReferenceTest {public static void main(String[] args) {AtomicReference<User> u = new AtomicReference<>();User user1 = new User("金厂长",35);User user2 = new User("木易阿婆",26);User user3 = new User("快乐的小lau",22);u.set(user1);// 查看当前对象并设为新对象user2System.out.println("当前对象为:"+u.getAndSet(user2)+",并设置新对象为user2");System.out.println("当前对象为:"+u.get());System.out.println("如果当前对象为user2,就把当前对象设为user3,否则不操作");u.compareAndSet(user2,user3); //如果当前对象为user2则把当前对象设为user3System.out.println("当前对象为:"+u.get());}
}

四、AtomicStampedReference与AtomicMarkableReference类

ABA问题:简单讲就是多线程环境,2次读写中一个线程修改A->B,然后又B->A,另一个线程看到的值未改变,又继续修改成自己的期望值。当然我们如果不关心过程,只关心结果,那么这个就是无所谓的ABA问题。

  • 为了解决ABA问题,伟大的java为我们提供了AtomicMarkableReference和AtomicStampedReference类,为我们解决了问题
  • AtomicStampedReference是利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了,在这里我借鉴一下别人举得例子

举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。这就是AtomicStampedReference的解决方案。

public class AtomicStampedReferenceTest {public static void main(String[] args) throws InterruptedException{final Integer init_Ref = 0, init_Stamp = 0;AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(init_Ref,init_Stamp);System.out.println("init_Ref为:"+asr.getReference() + "  ======  init_Stamp为:"+asr.getStamp());Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = asr.getReference();Integer stamp = asr.getStamp();//与前面AtomicReference的compareAndSet不同的是,增加了一个stamp标记比较,ref与stamp同时与// 当前的ref、stamp相同时才进行 + 操作System.out.println(ref + "  ======  " + stamp + "  ======  "+ asr.compareAndSet(ref, ref + 10, stamp, stamp + 1));}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = asr.getReference();// 当前的ref相同,但此时版本号不同,操作不执行返回falseSystem.out.println(ref + "  ======  " + init_Stamp + "  ======  "+ asr.compareAndSet(ref, ref + 10, init_Stamp, init_Stamp + 1));}});t1.start();t1.join(); //只是为了让代码有序执行t2.start();t2.join();System.out.println("最终的结果为:"+ asr.getReference() + "  ======  " + asr.getStamp());}
}


注:以上AtomicStampedReference部分摘自参考文章1, 本来看到这部分就想放弃了,但是翻到了这篇文章,讲的挺清晰的,就又继续看下去了。

可以看出,第一次ref、stamp都与输入值相等,因此执行ref+10,和stamp+1,此时ref=10,stamp=1。第二个线程中输入的ref与当前ref值相同,但是init_Stamp=0 与当前stamp=1 不等,因此不执行。总的来说就是除了对比ref,又增加了一个stamp来判断到底操不操作。

AtomicMarkableReference与AtomicStampedReference不同的是将int stamp改为了boolean类型的mark做标记。同样的例子:

public class AtomicMarkableReferenceTest {public static void main(String[] args) throws InterruptedException{final Integer init_Ref = 0;final Boolean init_Mark = false;AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(init_Ref,init_Mark);System.out.println("init_Ref为:"+amr.getReference() + "  ======  init_Mark为:"+amr.isMarked());Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = amr.getReference();Boolean mark = amr.isMarked();//与前面AtomicReference的compareAndSet不同的是,增加了一个stamp标记比较,ref与stamp同时与// 当前的ref、stamp相同时才进行 + 操作System.out.println(ref + "  ======  " + mark + "  ======  "+ amr.compareAndSet(ref, ref + 10, mark, true));}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = amr.getReference();// 当前的ref相同,但此时版本号不同,操作不执行返回falseSystem.out.println(ref + "  ======  " + init_Mark + "  ======  "+ amr.compareAndSet(ref, ref + 10, init_Mark, true));}});t1.start();t1.join(); //只是为了让代码有序执行t2.start();t2.join();System.out.println("最终的结果为:"+ amr.getReference() + "  ======  " + amr.isMarked());}
}

五、对象属性修改类型

以AtomicIntegerFieldUpdater 为例介绍一下简单使用方法:

public class User {private String name;volatile int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(final String name) {this.name = name;}public int getAge() {return this.age;}public void setAge(final int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class AtomicIntegerFieldUpdaterTest {public static void main(String[] args) {User user = new User("菜鸡",28);AtomicIntegerFieldUpdater<User> u = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");u.addAndGet(user,5);System.out.println(user.toString());}
}


这里值得注意的是:使用AtomicIntegerFieldUpdater.newUpdater修改属性时:

  1. 被修改的属性必须是volatile类型的,在线程之间共享变量时保证立即可见,
  2. 属性的修饰符(public/protected/default/private)要保证当前操作对该属性可以直接进行,比如当我们用private volatile int age 时就会报错,因为private修饰时,外部无法访问也无法修改。
  3. 只能是实例变量,不能是类变量,也就是说不能加static关键字。
  4. 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
  5. 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。

具体分析可以参考:参考文章3


参考文章:
  1. https://blog.csdn.net/zhaozhirongfree1111/article/details/72781758
  2. https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484834&idx=1&sn=7d3835091af8125c13fc6db765f4c5bd&source=41#wechat_redirect
  3. https://blog.csdn.net/zhaozhirongfree1111/article/details/72781147

Atomic原子类常用方法总结(包含四大类型)相关推荐

  1. JUC多线程:Atomic原子类与CAS原理

    一.Atomic 原子类的原理: Atomic 原子操作类是基于无锁 CAS + volatile 实现的,并且类中的所有方法都使用 final 修饰,进一步保证线程安全.而 CAS 算法的具体实现方 ...

  2. atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题

    本文基于JDK1.8 Atomic原子类 原子类是具有原子操作特征的类. 原子类存在于java.util.concurrent.atmic包下. 根据操作的数据类型,原子类可以分为以下几类. 基本类型 ...

  3. Atomic原子类和Unsafe魔法类 详解

    文章目录 1. Atomic原子类 1.1 Atomic原子类的作用 1.2 原子更新基本类型类 1.3 原子更新数组类 1.4 原子更新引用类型 1.5 原子更新字段类 2. Unsafe魔法类 2 ...

  4. Atomic原子类及原理

    目录 1 前言 2 unsafe类对Atomic原子类的支持 3 AtomicInteger的内部实现 3.1 准备 3.2 读 3.3 写 4 CAS机制 4.1 基本操作数 4.2 例子 4.3 ...

  5. Atomic 原子类

    1 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的.即使是 ...

  6. Java多线程进阶面试-Atomic 原子类

    1.介绍一下 Atomic 原子类 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的. ...

  7. 「死磕Java并发编程」说说Java Atomic 原子类的实现原理

    <死磕 Java 并发编程>系列连载中,大家可以关注一波. 「死磕 Java 并发编程」阿里二面,面试官:说说 Java CAS 原理? 「死磕 Java 并发编程」面试官:说说什么是 J ...

  8. 关于JCU并发包中的Atomic原子类及其CAS

    希望造成叫醒你的是梦想 而不是闹钟 目录 类型 常用方法 CAS CAS与Atomic原子类 自旋锁 CAS缺点 Atomic原子类: 在化学中,原子是构成一般物质的最小单位,是不可分割的.而在这里, ...

  9. atomic原子类实现机制_JUC学习笔记--Atomic原子类

    Atomic 原子操作类包 Atomic包 主要是在多线程环境下,无锁的进行原子操作.核心操作是基于UnSafe类实现的CAS方法 CAS CAS: compareAndSwap :传入两个值:期望值 ...

最新文章

  1. Linux_信号与信号量【转】
  2. 安卓(android)建立项目时失败,出现Android Manifest.xml file missing几种解决方法?...
  3. vue + element ui 的后台管理系统框架_从零开始搭建 VUE + Element UI后台管理系统框架...
  4. 代码生成工具CodeSmith中SchemaExplorer类API文档[转]
  5. Spring 5 新增全新的reactive web框架:webflux
  6. html入门moz a,css 让文字不被选中之-moz-user-select 属性介绍
  7. 20155222卢梓杰 实验四 恶意代码分析
  8. 常青:小程序音视频能力再升级
  9. 计算机内存的故障,计算机内存出现故障的解决方法
  10. htt://3g.hn_根据我对“询问HN:谁在招聘?”的分析,开发人员技能发展趋势
  11. ubuntu 16.04安装mysql_Ubuntu 16.04 安装mysql 5.7.16
  12. 剑指offer:二叉树打印成多行(层次遍历)
  13. [opencv] Unsupported depth of input image
  14. 3D世界相机防抖杆的机制探究
  15. 极客大学架构师训练营 加密技术 高可用系统的度量 高可用系统的架构 高可用系统的运维 第22课 听课总结
  16. 详解两个栈实现一个队列(python实现——经典面试题)
  17. 【有关数据库的问题】运行时错误‘3706’:未找到提供程序。该程序可能未正确安装。
  18. 量子纠缠在量子计算机中的作用,解密量子计算机,量子叠加和量子纠缠是制胜关键...
  19. 钟表维修管理系统技术解析(三) 工单录入
  20. There was an error checking the latest version of pip

热门文章

  1. 解读:一种来自Facebook团队的大规模时间序列预测算法(附github链接)
  2. AAAI论文首发:几何驱动的自监督的人体3D姿态估计方法
  3. 神经网络调参技巧:warmup策略
  4. CTR模型越来越深,如何让它变轻?
  5. 互联网1分钟 | 0328 阿里巴巴收购企业协作软件Teambition;完美世界:与谷歌达成战略合作,积极探索VR等新游戏类型...
  6. 直播技术:从性能参数到业务大数据,浅谈直播CDN服务监控
  7. 少侠请重新来过 - Vue学习笔记(二) - Vue生命周期
  8. 华为离职副总裁徐家骏:透露年薪千万的工作感悟,太震撼了!
  9. Spring 5 新特性:函数式Web框架
  10. xdebug影响php运行速度