金点网络-全网资源,一网打尽
  • 网站首页
    • 金点部落
    • 小游戏
    • OpenAPI
    • 设计资产导航
    • 升级会员
  • 技能学习
    • 体育运动
    • 办公教程
    • 口才演讲
    • 小吃技术
    • 建站教程
    • 摄影教程
    • 棋牌教程
    • 网赚教程
      • 爆粉引流
      • 自媒体
      • 贴吧引流
  • 网站源码
    • 商城/淘客/交易
    • 小说/漫画/阅读
    • 影视/音乐/视频
    • 微信/微商/微擎
    • 理财/金融/货币
    • 模板/主题/插件
  • 游戏源码
    • 精品网单
    • 端游源码
    • 手游源码
    • 页游源码
  • 素材资料
    • 电子文档
    • 综合资料
    • 考研资料
    • 设计素材
    • 音频讲座
      • 人文艺术
      • 名师讲座
      • 说书小说
  • 软件工具
    • Windows软件
    • MacOS软件
    • Android软件
  • 寄售资源
    • 游戏源码
    • 网站源码
    • 软件源码
  • 公益服
登录/注册
  • 专享大神特权
立即开通开通会员抄底价

Java 并发编程·Atom 原子类

作者 : jamin 本文共6417个字,预计阅读时间需要17分钟 发布时间: 2020-10-18 共1450人阅读

Atom 原子类

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

CAS 操作

什么是 CAS

CAS(compare and swap), 比较并交换。使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而 CAS 操作(又称无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用 CAS 又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

CAS 的操作过程

CAS 比较交换的过程可以通俗的理解为 CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当 V 和 O 相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值 O 就是目前来说最新的值了,自然而然可以将新值 N 赋值给 V。反之,V 和 O 不相同,表明该值已经被其他线程改过了则该旧值 O 不是最新版本的值了,所以不能将新值 N 赋给 V,返回 V 即可。当多个线程使用 CAS 操作一个变量是,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。

Synchronized VS CAS

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

CAS 存在的问题

1. ABA 问题

因为 CAS 会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值 A 变为了成 B,然后再变成 A,刚好在做 CAS 时检查发现旧值并没有变化依然为 A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径 A->B->A 就变成了 1A->2B->3C。

2.自旋时间过长

使用 CAS 时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。

原子更新基本类型

atomic 包提高原子更新基本类型的工具类有:

  • AtomicBoolean:以原子更新的方式更新 Boolean;
  • AtomicInteger:以原子更新的方式更新 Integer;
  • AtomicLong:以原子更新的方式更新 Long。

这几个类的用法基本一致,这里以 AtomicInteger 为例总结常用的方法:

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

为了能够弄懂 AtomicInteger 的实现原理,以 getAndIncrement 方法为例,来看下源码:

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

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

private static final Unsafe unsafe = Unsafe.getUnsafe();

Unsafe 类在 sun.misc 包下,Unsafer 类提供了一些底层操作,atomic 包下的原子操作类的也主要是通过 Unsafe 类提供的 compareAndSwapInt,compareAndSwapLong 等一系列提供 CAS 操作的方法来进行实现,并且由于 CAS 是采用乐观锁策略,因此,这种数据更新的方法也具有高效性。

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 包下提供能原子更新数组中元素的类有:

  • AtomicIntegerArray:原子更新整型数组中的元素;
  • AtomicLongArray:原子更新长整型数组中的元素;
  • AtomicReferenceArray:原子更新引用类型数组中的元素。

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

  • addAndGet(int i, int delta):以原子更新的方式将数组中索引为 i 的元素与输入值相加;
  • getAndIncrement(int i):以原子更新的方式将数组中索引为 i 的元素自增加 1;
  • compareAndSet(int i, int expect, int update):将数组中索引为 i 的位置的元素进行更新。

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

原子更新引用类型

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

  • AtomicReference:原子更新引用类型;
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
  • AtomicMarkableReference:原子更新带有标记位的引用类型。
public class AtomicDemo {
    private static AtomicReference 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); // User{userName='a', age=1}
        System.out.println(reference.get()); // User{userName='b', age=2}
    }

    static class User {
        private String userName;
        private int age;

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }
}

原子更新字段类型

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

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

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

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

这几个类提供的方法基本一致,以 AtomicIntegerFieldUpdater 为例来看看具体的使用:

public class AtomicDemo {
  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); // 1
      System.out.println(updater.get(user)); // 6
  }

  static class User {
      private String userName;
      public volatile int age;

      public User(String userName, int age) {
          this.userName = userName;
          this.age = age;
      }
  }
}

LongAdder

public class AtomicLongDemo {

    public static void main(String[] args) {
        AtomicLong counter = new AtomicLong(0);
        ExecutorService service = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while (!service.isTerminated()) {

        }
        long end = System.currentTimeMillis();
        System.out.println("AtomicLong 运算结果:" + counter.get() + ",耗时:" + (end - start));
    }

    private static class Task implements Runnable {

        private AtomicLong counter;

        Task(AtomicLong counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.incrementAndGet();
            }
        }
    }
}
AtomicLong 运算结果:100000000,耗时:1916
public class LongAdderDemo {

    public static void main(String[] args) {
        LongAdder counter = new LongAdder();
        ExecutorService service = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while (!service.isTerminated()) {
        }
        long end = System.currentTimeMillis();
        System.out.println("LongAdder 运算结果:" + counter.sum() + ",耗时:" + (end - start));
    }

    private static class Task implements Runnable {
        private LongAdder counter;

        Task(LongAdder counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        }
    }
}
LongAdder 运算结果:100000000,耗时:133

运行速度提升不止十倍!在小并发的环境下,论更新的效率,两者都差不多。但是高并发的场景下,LongAdder 有着明显更高的吞吐量,但是有着更高的空间复杂度(以空间换时间)。当我们的场景是为了统计计数,而不是为了更细粒度的同步控制时,并且是在多线程更新的场景时,LongAdder 类比 AtomicLong 更好用。

虽然高并发下 LongAdder 效率远高于 AtomicLong,但 LongAdder 并不能完全替代 AtomicLong,LongAdder 适合的场景是统计求和计数的场景,而且 LongAdder 基本只提供了 add 方法,而 AtomicLong 还具有 CAS 方法。

LongAccumulator

LongAdder 类是 LongAccumulator 的一个特例,LongAccumulator 提供了比 LongAdder 更强大的功能,如下构造函数根据输入的两个参数返回一个计算值,identity 则是 LongAccumulator 累加器的初始值。

public class LongAccumulatorDemo {

    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator((x, y) -> 2 + x * y, 1);
        ExecutorService executor = Executors.newFixedThreadPool(8);
        IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println(accumulator.getThenReset());
    }
}

LongAdder 其实是 LongAccumulator 的一个特例,new LongAdder() 的调用相当于 new LongAccumulator((x, y) -> x + y, 0)。前者初始值只能默认为 0,后者可以指定累加规则比如不是累加而是相乘,只需要构造 LongAccumulator 时候传入自定义双面运算器就 OK,前者则内置累加的规则。

LongAccumulator will work correctly if we supply it with a commutative function where the order of accumulation does not matter.

只有不在乎执行顺序时才适合使用 LongAccumulator。

Atom ExecutorService ForkJoin Java ReentrantLock synchronized ThreadLocal volatile 后台
本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
金点网络-全网资源,一网打尽 » Java 并发编程·Atom 原子类

常见问题FAQ

免费下载或者VIP会员专享资源能否直接商用?
本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
是否提供免费更新服务?
持续更新,永久免费
是否经过安全检测?
安全无毒,放心食用
jamin

jamin 大神

下一篇
网络协议-组播介绍

相关推荐

Spring Cloud Alibaba

Spring Cloud Alibaba

梦幻修仙2 一键即玩端 电脑架设 网页游戏 回合制 单机 非联网

梦幻修仙2 一键即玩端 电脑架设 网页游戏 回合制 单机 非联网

Spring Boot 定时任务

Spring Boot 定时任务

梦幻龙族2【免虚拟机一键端】GM后台优化版

梦幻龙族2【免虚拟机一键端】GM后台优化版

Java 理论概念·Cookie 和 Session

Java 理论概念·Cookie 和 Session

标签云
Android Atom ExecutorService ForkJoin GM GM后台 GM授权后台 H5 Java Javascript Linux手工服务端 pipbestcom Python ReentrantLock synchronized ThreadLocal volatile Win一键即玩服务端 一键端 传奇 写作 创业 单机 后台 商业端 外网 安卓 安卓苹果双端 工具 手工端 手游 搭建教程 教程 数据分析 文案 游戏源码 端游 经典 网单 职场 自媒体 视频教程 详细搭建教程 运营后台 页游

近期文章

  • 回合手游【逍遥西游之繁华西游】最新整理单机一键既玩镜像服务端_Linux手工端_GM后台_教程
  • 最新整理精品回合制手游【天书奇谈3D混沌完整版】VM一键单机版_linux手工外网端_隐盟视频教程_授权GM后台_双端
  • 典藏修真页游【诸仙列传OL】最新整理Win系服务端_GM工具_详细外网搭建教程
  • MT3换皮MH【浮生若梦尊享挂机修复版】最新整理单机一键即玩镜像端_Linux手工服务端_安卓苹果双端_GM后台_详细搭建教程
  • 大话回合手游【最新引擎之缥缈西游渡劫版】最新整理Linux手工服务端_安卓苹果双端_管理后台_CDK后台_详细搭建教程_视频教程

分类

  • | wordpress插件 |
  • | wordpress模板 |
  • | 其它模板 |
  • | 帝国模板 |
  • | 织梦插件 |
  • | 织梦模板 |
  • A5源码
  • Android软件
  • APP引流
  • E语言
  • H5
  • LUA
  • QQ营销
  • SEO推广
  • Windows软件
  • 体育运动
  • 信息数据
  • 创业专题
  • 办公教程
  • 口才演讲
  • 名师讲座
  • 商城/淘客/交易
  • 小吃技术
  • 小说/漫画/阅读
  • 建站教程
  • 引流脚本
  • 影视/音乐/视频
  • 影视资源
  • 微信/微商/微擎
  • 微信小程序
  • 微信营销
  • 微擎模块
  • 手游源码
  • 技能学习
  • 抖音课程
  • 摄影教程
  • 棋牌教程
  • 模板/主题/插件
  • 游戏源码
  • 爆粉引流
  • 理财/金融/货币
  • 生活老师
  • 电商客
  • 电子文档
  • 电脑教程
  • 社群营销
  • 站长工具
  • 精品网单
  • 系统工具
  • 素材资料
  • 综合资料
  • 编程经验
  • 网站源码
  • 网络安全
  • 网赚教程
  • 网赚源码
  • 考研资料
  • 脚本/AI/智能
  • 自媒体
  • 英语学习
  • 营销软件
  • 设计素材
  • 说书小说
  • 贴吧引流
  • 软件工具
  • 软文营销
  • 逆向软件
  • 音频讲座
  • 页游源码

提供最优质的资源集合

立即加入 友好社区
金点网络-全网资源,一网打尽

新一代全网资源综合门户网(www.pipbest.com-金点网络)专注服务于互联网,提供各类最新最全的免费源码下载(PHP、ASP、JSP、.NET),更提供免费工具,免费源码下载,软件下载,素材下载,赚钱教程下载,交流论坛等网站运营相关的一切内容,为网友搜罗最有价值的网站源码下载与技术教程等服务!

服务目录
  • 金点OpenAPI
  • 金点云
  • 金点支付
友情链接
  • 数媒派
  • 国家电网
快速搜索

本站由Nice强力驱动

声明: 本站部分内容属于原创转载请注明出处 如有侵权行为请严格参照本站【版权声明】与我们联系,我们将在48小时内容进行处理!

本站部分内容属于原创转载请注明出处 如有侵权行为请严格参照本站【版权声明】与我们联系,我们将在48小时内容进行处理!
© 2016-2023 PipBest.Com - 金点网络 & 金点部落. All rights reserved 京ICP备2022005359号-1
  • 关注有礼
  • 签到
  • 客服
    官方QQ群 常见问题 FAQ

    在线客服

    点我联系

    直接说出您的需求!
    切记!带上资源连接与问题!

    工作时间: 9:30-21:30

  • 暗黑
    模式
  • 全屏
  • 投稿
    赚钱
  • 首页

  • 签到

  • 切换

  • 客服

金点网络-全网资源,一网打尽
  • 登录
  • 注册
or
or
忘记密码?
金点网络-全网资源,一网打尽
  • 网站首页 ►
    • 金点部落
    • 小游戏
    • OpenAPI
    • 设计资产导航
    • 升级会员
  • 技能学习 ►
    • 体育运动
    • 办公教程
    • 口才演讲
    • 小吃技术
    • 建站教程
    • 摄影教程
    • 棋牌教程
    • 网赚教程 ►
      • 爆粉引流
      • 自媒体
      • 贴吧引流
  • 网站源码 ►
    • 商城/淘客/交易
    • 小说/漫画/阅读
    • 影视/音乐/视频
    • 微信/微商/微擎
    • 理财/金融/货币
    • 模板/主题/插件
  • 游戏源码 ►
    • 精品网单
    • 端游源码
    • 手游源码
    • 页游源码
  • 素材资料 ►
    • 电子文档
    • 综合资料
    • 考研资料
    • 设计素材
    • 音频讲座 ►
      • 人文艺术
      • 名师讲座
      • 说书小说
  • 软件工具 ►
    • Windows软件
    • MacOS软件
    • Android软件
  • 寄售资源
    ►
    • 游戏源码
    • 网站源码
    • 软件源码
  • 公益服
×
u3** 刚刚下载了 爆款吸金文案训练

    全网资源·一网打尽

  • 金点出品,必属精品!
  • 发布原创内容,获取高额提成!
  • 我们与你共创美好数字生态!
  • 无特殊说明密码默认:pipbest.com