首页 > 作文

Java并发编程之原子性

更新时间:2023-04-06 04:01:35 阅读: 评论:0

线程安全

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

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

原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作可见性:一个线程对主内存的修改可以及时的被其他线程观察到有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

juc中的atomic包详解

atomic包中提供了很多atomicxxx的类:

它们都是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 {        executorrvice executorrvice = executors.newcachedthreadpool();        final maphore maphore = new maphore(threadtotal);        final countdownlatch countdownlatch = new countdownlatch(clienttotal);        for (int i = 0; i < clienttotal ; i++) {            executorrvice.execute(() -> {                try {                    maphore.acquire();                    add();                    maphore.relea();                } catch (exception e) {                    log.error("exception", e);                }                countdownlatch.countdown();            });        }        countdo守株待兔什么意思wnlatch.await();        executorrvice.shutdown();        log.info("count:{}", count.get());    }     private static void add() {        count.incrementandget();    }}

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

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

public class atomicinteger extends number implements java.io.rializable {    private static final long rialversionuid = 6214790243416807050l;     // 发糕的做法松软又好吃tup to u unsafe.compareandswapint for updates    private static final unsafe unsafe = unsafe.getunsafe();    private static final long valueofft;     static {        try {            valueofft = unsafe.objectfieldofft                (atomicinteger.class.getdeclaredfield("value"));        } catch (exception ex) { throw new error(ex); }    }     private volatile int value;      ... 此处省略多个方法...     /**     * atomically increments by one the current value.     *     * @return the updated value     */    public final int incrementandget() {        return unsafe.getandaddint(this, valueofft, 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 getintvolat撒切尔夫人ile(object var1, long var2);}

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

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

更新后:

compareandswapint()方法的第一个参数(var1)是当前的对象,就是代码示例中的count。此时它的值为0(期望值)。第二个值(var2)是传递的valueofft值,它的值为12。第三个参数(var4)就为常量1。方法中的变量参数(var5)是根据参数一和参数二valueofft,调用底层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的基础上将单点的更新压力分散到各个节点上,在低并发的时候对ba的直接更新可以很好的保障跟atomic的性能基本一致。而在高并发的时候,通过分散提高了性能。但是如果在统计的时候有并发更新,可能会导致统计的数据有误差。

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

下面简单的演示下atomic包下atomicreference简单的用法:

@slf4jpublic class atomicexample4 {     private static atomicreference<integer> count = new atomicreference<>(0);     public static void main(string[] args) {        count.compareandt(0, 2);         count.compareandt(0, 1);         log.info("count:{}", count.get());    }}

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

上面的第一个方法可以将值更新为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.compareandt(example5, 100, 120)) {            log.info("update success 1, {}", example5.getcount());        }         if (updater.compareandt(example5, 100, 120)) {            log.info("update success 2, {}", example5.getcount());        } el {            log.info("update failed, {}", example5.getcount());        }    }}

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

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

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

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

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

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

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 compareandt(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 impleme我的心爱之物五年级作文300字nts java.io.rializable {    private static final long rialversionuid = -2308431214976778248l;     private static final unsafe unsafe = unsafe.getunsafe();     ...此处省略....      /**     * atomically ts the element at position {@code i} to the given value     * and returns the old value.     *     * @param i the index     * @param newvalue the new value     * @return the previous value     */    public final long getandt(int i, long newvalue) {        return unsafe.getandtlong(array, checkedbyteofft(i), newvalue);    }     /**     * atomically ts the element at position {@code i} to the given     * updated value if the current value {@code ==} the expected value.     *     * @param i the index     * @param expect the expected value     * @param update the new value     * @return {@code true} if successful. fal return indicates that     * the actual value was not equal to the expected value.     */    public final boolean compareandt(int i, long expect, long update) {        return compareandtraw(checkedbyteofft(i), expect, update);    }}

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

@slf4jpublic class atomicexample6 {     private static atomicboolean ishappened = new atomicboolean(fal);     // 请求总数    public static int clienttotal = 5000;     // 同时并发执行的线程数    public static int threadtotal = 200;     public static void main(string[] args) throws exception {        executorrvice executorrvice = executors.newcachedthreadpool();        final maphore maphore = new maphore(threadtotal);        final countdownlatch countdownlatch = new countdownlatch(clienttotal);        for (int i = 0; i < clienttotal ; i++) {            executorrvice.execute(() -> {                try {                    maphore.acquire();                    test();                    maphore.relea();                } catch (exception e) {                    log.error("exception", e);                }                countdownlatch.countdown();            });        }        countdownlatch.await();        executorrvice.shutdown();        log.info("ishappened:{}", ishappened.get());    }     private static void test() {        if (ishappened.compareandt(fal, true)) {            log.info("execute");        }    }}

总结

以上就是atomic包的基本原理及主要的使用方法。它是使用cas来保证原子性操作,从而达到线程安全的目的。

仅为个人经验,希望能给大家一个参考,也希望大家多多支持www.887551.com。

本文发布于:2023-04-06 04:01:34,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/b8994b0d95561a4c7c67cadf0d23e827.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:Java并发编程之原子性.doc

本文 PDF 下载地址:Java并发编程之原子性.pdf

标签:线程   方法   变量   操作
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图