广告
返回顶部
首页 > 资讯 > 精选 >Java的原子性Atomic如何使用
  • 453
分享到

Java的原子性Atomic如何使用

2023-06-29 12:06:47 453人浏览 安东尼
摘要

这篇文章主要介绍“Java的原子性Atomic如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java的原子性Atomic如何使用”文章能帮助大家解决问题。线程安全当多个线程访问某个类时,不管

这篇文章主要介绍“Java的原子性Atomic如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java的原子性Atomic如何使用”文章能帮助大家解决问题。

线程安全

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么就称这个类时线程安全的。

线程安全主要体现在以下三个方面

  • 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作

  • 可见性:一个线程对主内存的修改可以及时的被其他线程观察到

  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

JUC中的Atomic包详解

Atomic包中提供了很多Atomicxxx的类:

Java的原子性Atomic如何使用

它们都是CAS(compareAndSwap)来实现原子性。

先写一个简单示例如下:

@Slf4jpublic class AtomicExample1 {     // 请求总数    public static int clientTotal = 5000;     // 同时并发执行的线程数    public static int threadTotal = 200;     public static AtomicInteger count = new AtomicInteger(0);     public static void main(String[] args) throws Exception {        ExecutorService executorService = Executors.newCachedThreadPool();        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) {                    log.error("exception", e);                }                countDownLatch.countDown();            });        }        countDownLatch.await();        executorService.shutdown();        log.info("count:{}", count.get());    }     private static void add() {        count.incrementAndGet();    }}

可以发下每次的运行结果总是我们想要的预期结果5000。说明该计数方法是线程安全的。

我们查看下count.incrementAndGet()方法,它的第一个参数为对象本身,第二个参数为valueOffset是用来记录value本身在内存的编译地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较,第三个参数为常量1

public class AtomicInteger extends Number implements java.io.Serializable {    private static final long serialVersionUID = 6214790243416807050L;     // setup to use Unsafe.compareAndSwapint for updates    private static final Unsafe unsafe = Unsafe.getUnsafe();    private static final long valueOffset;     static {        try {            valueOffset = unsafe.objectFieldOffset                (AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }     private volatile int value;      ... 此处省略多个方法...         public final int incrementAndGet() {        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    }}

AtomicInteger源码里使用了一个Unsafe的类,它提供了一个getAndAddInt的方法,我们继续点看查看它的源码:

public final class Unsafe {    private static final Unsafe theUnsafe;     ....此处省略很多方法及成员变量....   public final int getAndAddInt(Object var1, long var2, int var4) {        int var5;        do {            var5 = this.getIntVolatile(var1, var2);        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));         return var5;    }   public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);  public native int getIntVolatile(Object var1, long var2);}

可以看到这里使用了一个do while语句来做主体实现的。而在while语句里它的核心是调用了一个compareAndSwapInt()的方法。它是一个native方法,它是一个底层的方法,不是使用Java来实现的。

假设我们要执行0+1=0的操作,下面是单线程情况下各参数的值:

Java的原子性Atomic如何使用

Java的原子性Atomic如何使用

更新后:

Java的原子性Atomic如何使用

compareAndSwapInt()方法的第一个参数(var1)是当前的对象,就是代码示例中的count。此时它的值为0(期望值)。第二个值(var2)是传递的valueOffset值,它的值为12。第三个参数(var4)就为常量1。方法中的变量参数(var5)是根据参数一和参数二valueOffset,调用底层getIntVolatile方法得到的值,此时它的值为0 。compareAndSwapInt()想要达到的目标是对于count这个对象,如果当前的期望值var1里的value跟底层的返回的值(var5)相同的话,那么把它更新成var5+var4这个值。不同的话重新循环取期望值(var5)直至当前值与期望值相同才做更新。compareAndSwap方法的核心也就是我们通常所说的CAS。

Atomic包下其他的类如AtomicLong等的实现原理基本与上述一样。

这里再介绍下LongAdder这个类,通过上述的分析,我们已经知道了AtomicLong使用CAS:在一个死循环内不断尝试修改目标值直到修改成功。如果在竞争不激烈的情况下,它修改成功概率很高。反之,如果在竞争激烈的情况下,修改失败的概率会很高,它就会进行多次的循环尝试,因此性能会受到一些影响。

对于普通类型的long和double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。LongAdder的核心思想是将热点数据分离,它可以将AtomicLong内部核心数据value分离成一个数组,每个线程访问时通过hash等算法映射到其中一个数字进行计数。而最终的计数结果则为这个数组的求和累加,其中热点数据value,它会被分离成多个单元的cell,每个cell独自维护内部的值,当前对象的实际值由所有的cell累计合成。这样,热点就进行了有效的分离,提高了并行度。LongAdder相当于在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候对base的直接更新可以很好的保障跟Atomic的性能基本一致。而在高并发的时候,通过分散提高了性能。但是如果在统计的时候有并发更新,可能会导致统计的数据有误差。

在实际高并发计数的时候,可以优先使用LongAdder。在低并行度或者需要准确数值的时候可以优先使用AtomicLong,这样反而效率更高。

下面简单的演示下Atomic包下AtomicReference简单的用法:

@Slf4jpublic class AtomicExample4 {     private static AtomicReference<Integer> count = new AtomicReference<>(0);     public static void main(String[] args) {        count.compareAndSet(0, 2);         count.compareAndSet(0, 1);         log.info("count:{}", count.get());    }}

compareAndSet()分别传入的是预期值跟更新值,只有当预期值跟当前值相等时,才会将值更新为更新值;

上面的第一个方法可以将值更新为2,而第二个步中无法将值更新为1。

下面简单介绍下AtomicIntegerFieldUpdater 用法(利用原子性去更新某个类的实例):

@Slf4jpublic class AtomicExample5 {     private static AtomicIntegerFieldUpdater<AtomicExample5> updater =            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");     @Getter    private volatile int count = 100;     public static void main(String[] args) {         AtomicExample5 example5 = new AtomicExample5();         if (updater.compareAndSet(example5, 100, 120)) {            log.info("update success 1, {}", example5.getCount());        }         if (updater.compareAndSet(example5, 100, 120)) {            log.info("update success 2, {}", example5.getCount());        } else {            log.info("update failed, {}", example5.getCount());        }    }}

它可以更新某个类中指定成员变量的值。

注意:修改的成员变量需要用volatile关键字来修饰,并且不能是static描述的字段。

AtomicStampReference这个类它的核心是要解决CAS的ABA问题(CAS操作的时候,其他线程将变量的值A改成了B,接着又改回了A,等线程使用期望值A与当前变量进行比较的时候,发现A变量没有变,于是CAS就将A值进行了交换操作。

实际上该值已经被其他线程改变过)。

ABA问题的解决思路就是每次变量变更的时候,就将版本号加一。

看一下它的一个核心方法compareAndSet():

public class AtomicStampedReference<V> {     private static class Pair<T> {        final T reference;        final int stamp;        private Pair(T reference, int stamp) {            this.reference = reference;            this.stamp = stamp;        }        static <T> Pair<T> of(T reference, int stamp) {            return new Pair<T>(reference, stamp);        }    }    ... 此处省略多个方法 ....    public boolean compareAndSet(V   expectedReference,                                 V   newReference,                                 int expectedStamp,                                 int newStamp) {        Pair<V> current = pair;        return            expectedReference == current.reference &&            expectedStamp == current.stamp &&            ((newReference == current.reference &&              newStamp == current.stamp) ||             casPair(current, Pair.of(newReference, newStamp)));    }}

可以看到它多了一个stamp的比较,stamp的值是由每次更新的时候进行维护的。

再介绍下AtomicLongArray,它维护了一个数组。在该数组下,我们可以选择性的已原子性操作更新某个索引对应的值。

public class AtomicLongArray implements java.io.Serializable {    private static final long serialVersionUID = -2308431214976778248L;     private static final Unsafe unsafe = Unsafe.getUnsafe();     ...此处省略....          public final long getAndSet(int i, long newValue) {        return unsafe.getAndSetLong(array, checkedByteOffset(i), newValue);    }         public final boolean compareAndSet(int i, long expect, long update) {        return compareAndSetRaw(checkedByteOffset(i), expect, update);    }}

最后再写一个AtomcBoolean的简单使用:

@Slf4jpublic class AtomicExample6 {     private static AtomicBoolean isHappened = new AtomicBoolean(false);     // 请求总数    public static int clientTotal = 5000;     // 同时并发执行的线程数    public static int threadTotal = 200;     public static void main(String[] args) throws Exception {        ExecutorService executorService = Executors.newCachedThreadPool();        final Semaphore semaphore = new Semaphore(threadTotal);        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);        for (int i = 0; i < clientTotal ; i++) {            executorService.execute(() -> {                try {                    semaphore.acquire();                    test();                    semaphore.release();                } catch (Exception e) {                    log.error("exception", e);                }                countDownLatch.countDown();            });        }        countDownLatch.await();        executorService.shutdown();        log.info("isHappened:{}", isHappened.get());    }     private static void test() {        if (isHappened.compareAndSet(false, true)) {            log.info("execute");        }    }}

关于“Java的原子性Atomic如何使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

--结束END--

本文标题: Java的原子性Atomic如何使用

本文链接: https://www.lsjlt.com/news/324863.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java的原子性Atomic如何使用
    这篇文章主要介绍“Java的原子性Atomic如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java的原子性Atomic如何使用”文章能帮助大家解决问题。线程安全当多个线程访问某个类时,不管...
    99+
    2023-06-29
  • Java并发编程之原子性-Atomic的使用
    目录线程安全线程安全主要体现在以下三个方面JUC中的Atomic包详解总结线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中...
    99+
    2022-11-13
  • 如何在Java中使用atomic包
    如何在Java中使用atomic包?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Atomic包介绍在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型...
    99+
    2023-05-30
    java atomic
  • Java多线程 原子性操作类的使用
    目录1. 基本类型的使用2. 数组类型的使用3. 引用类型的使用 4.字段类型的使用前言: 在java5以后,我们接触到了线程原子性操作,也就是在修改时我们只需要保证它的那个瞬间是安...
    99+
    2022-11-12
  • AtomicReference原子类如何在Java中使用
    AtomicReference原子类如何在Java中使用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。AtomicReference介绍和函数列表AtomicReference...
    99+
    2023-05-31
    java concurrency ava
  • 如何理解java 并发中的原子性与可视性
    如何理解java 并发中的原子性与可视性?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java 并发中的原子性与可视性实例详解并发其实是一种解耦合的策略,它帮助我们把做什么...
    99+
    2023-05-31
    java 并发 原子性
  • MySQL的原子性是如何保证的?
    原子性是指一个事务中的所有操作要么全部成功提交,要么全部回滚撤销,不存在部分成功部分失败的情况。MySQL通过以下方式来保证事务的原子性: 事务日志(transaction log):MySQL使用...
    99+
    2023-09-23
    mysql 数据库
  • Java中如何使用原子组件和同步组件
    Java中如何使用原子组件和同步组件,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。原子组件的实现原理CAScas的底层实现可以看下之前写的一篇文章:详解锁原理,synchr...
    99+
    2023-06-15
  • 【漫画】JAVA并发编程 如何解决原子性问题
    原创声明:本文转载自公众号【胖滚猪学编程】,转载务必注明出处! 在并发编程BUG源头文章中,我们初识了并发编程的三个bug源头:可见性、原子性、有序性。在如何解决可见性和原子性文章中我们大致了解了可见性和有序性的解决思路,今天...
    99+
    2021-07-03
    【漫画】JAVA并发编程 如何解决原子性问题
  • Android的shape属性和子属性如何使用
    这篇文章主要讲解了“Android的shape属性和子属性如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android的shape属性和子属性如何使用”吧!shape属性详解<...
    99+
    2023-07-02
  • 如何在java中使用关闭钩子
    如何在java中使用关闭钩子?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分...
    99+
    2023-06-14
  • 如何使用Java实现经典游戏推箱子
    这篇文章将为大家详细讲解有关如何使用Java实现经典游戏推箱子,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。主要设计游戏面板生成显示地图生成算法人物移动算法播放背景音乐箱子移动算法全部箱子移动到指定位置,...
    99+
    2023-06-29
  • python中的Sobel算子如何使用
    本篇内容主要讲解“python中的Sobel算子如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“python中的Sobel算子如何使用”吧!说明Sobel算子根据像素点的上下、左右相邻点的...
    99+
    2023-06-20
  • 深入了解Java线程池的原理使用及性能优化
    目录1、什么是线程及线程池1.1、为什么要使用线程 1.2、为什么要使用线程池1.3、线程池的优点2、线程池在java中的使用2.1、线程池的工作原理2.2、线程池的jav...
    99+
    2023-05-17
    Java线程池原理 Java线程池使用 Java线程池
  • 如何使用Numpy优化Java算法的性能?
    在计算机科学领域中,算法的性能一直是一个非常关键的问题。对于一些算法来说,即使是微小的优化也可以显著地提高其性能。本文将介绍如何使用Numpy优化Java算法的性能,并提供实例代码。 Numpy是Python中一种用于科学计算的库,它可以...
    99+
    2023-08-23
    编程算法 unix numpy
  • CSS中如何使用box-align属性控制子元素布局
    这篇文章主要介绍CSS中如何使用box-align属性控制子元素布局,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!box-align 说明box-align属性, 指定元素内子要素纵...
    99+
    2022-10-19
  • 如何在Java中使用substring()函数截取子字符串
    这篇文章给大家介绍如何在Java中使用substring()函数截取子字符串,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 substring(int beginIndex) 形式此方式用于提取从索...
    99+
    2023-06-08
  • css如何利用transform的属性实现盒子居中
    这篇文章主要介绍css如何利用transform的属性实现盒子居中,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!利用transform的属性(缺点:需要支持Html5)<style type=&quo...
    99+
    2023-06-17
  • 如何使用MySQL和Java实现一个简单的电子签名功能
    如何使用MySQL和Java实现一个简单的电子签名功能导语:在我们的日常生活中,电子签名越来越常见。它可以在各种场合中使用,例如电子合同、电子收据和授权文件等。本文将介绍如何使用MySQL和Java来实现一个简单的电子签名功能,并提供具体的...
    99+
    2023-10-22
    MySQL Java 电子签名
  • 如何使用DB2 UDB的电子商务OLTP应用程序进行性能优化
    这篇文章给大家分享的是有关如何使用DB2 UDB的电子商务OLTP应用程序进行性能优化的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。  一、 监视开关  确保已经打开监视开关。如...
    99+
    2022-10-18
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作