Java原子操作类,你知道多少?_java 原子操作 哪些-程序员宅基地

技术标签: Java  Java原子操作类  

原子操作类简介

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

CAS

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。 这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。 硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。 CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。 当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

//著名的CAS
//var1是比较值所属的对象,var2需要比较的值(但实际是使用地址偏移量来实现的),
//如果var1对象中偏移量为var2处的值等于var4,那么将该处的值设置为var5并返回true,如果不等于var4则返回false。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  • CAS的问题

1.ABA问题

如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。

J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题, 它可以通过控制变量值的版本来保证 CAS 的正确性。 大部分情况下 ABA 问题不会影响程序并发的正确性, 如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。

2.自旋时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用, 第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源, 延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。 第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation) 而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3.只能保证一个共享变量的原子操作 CAS只对单个共享变量有效,当操作涉及跨多个共享变量时CAS无效。 但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性, 可以把多个变量封装成对象里来进行 CAS 操作. 所以我们可以使用锁或者利用AtomicReference类把多个共享变量封装成一个共享变量来操作。

  • synchronized VS CAS

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

原子更新基本类

atomic包提高原子更新基本类的工具类,如下:

AtomicBoolean //以原子更新的方式更新Boolean

AtomicIntege //以原子更新的方式更新Integer

AtomicLong //以原子更新的方式更新Long
  • 以AtomicInteger为例总结常用的方法:
addAndGet(int delta) //以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果

incrementAndGet() //以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果

getAndSet(int newValue) //将实例中的值更新为新值,并返回旧值

getAndIncrement() //以原子的方式将实例中的原值加1,返回的是自增前的旧值

AtomicInteger的getAndIncrement()方法源码如下:

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

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

private static final Unsafe unsafe = Unsafe.getUnsafe();
public class AtomicIntegerDemo {
    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    //java.util.concurrent.atomic.AtomicInteger;
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Semaphore和CountDownLatch模拟并发
        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) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count:{"+count.get()+"}");
    }

    public static void add() {
        count.incrementAndGet();
    }
}

输出结果:

count:{5000}

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包中只提供了对boolean,int ,long这三种基本类型的原子更新的方法, 参考对boolean更新的方式,原子更新char,doule,float也可以采用类似的思路进行实现。

原子更新数组

atomic包下提供能原子更新数组中元素的类有:

AtomicIntegerArray //原子更新整型数组中的元素

AtomicLongArray //原子更新长整型数组中的元素

AtomicReferenceArray //原子更新引用类型数组中的元素

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

getAndAdd(int i, int delta) //以原子更新的方式将数组中索引为i的元素与输入值相加

getAndIncrement(int i) //以原子更新的方式将数组中索引为i的元素自增加1

compareAndSet(int i, int expect, int update) //将数组中索引为i的位置的元素进行更新

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

public class AtomicIntegerArrayDemo {
    private static int[] value = new int[]{1, 2, 3};
    private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        //对数组中索引为1的位置的元素加5
        int result = integerArray.getAndAdd(1, 5);
        System.out.println(integerArray.get(1));
        System.out.println(result);
    }
}

输出结果:

7
2

原子更新引用类型

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

AtomicReference //原子更新引用类型

AtomicReferenceFieldUpdater //原子更新引用类型里的字段

AtomicMarkableReference //原子更新带有标记位的引用类型

这几个类的使用方法也是基本一样的,以AtomicReference为例。

public class AtomicReferenceDemo {

    private static AtomicReference<User> 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);
        System.out.println(reference.get());
    }

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

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

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

User{userName='a', age=1}
User{userName='b', age=2}

首先将对象User1用AtomicReference进行封装,然后调用getAndSet方法, 从结果可以看出,该方法会原子更新引用的user对象, 变为User{userName='b', age=2},返回的是原来的user对象User{userName='a', age=1}。

原子更新字段类型

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

AtomicIntegeFieldUpdater //原子更新整型字段类

AtomicLongFieldUpdater //原子更新长整型字段类

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

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

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

这几个类提供的方法基本一致,以AtomicIntegerFieldUpdater为例。

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

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

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

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

1
6

创建AtomicIntegerFieldUpdater是通过它提供的静态方法进行创建, getAndAdd方法会将指定的字段加上输入的值,并且返回相加之前的值。 user对象中age字段原值为1,加5之后,可以看出user对象中的age字段的值已经变成了6。

 

免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_20499001/article/details/90640734

智能推荐

Android获取当前网速质量——分四个等级_connectionclassmanager.getinstance().getcurrentban-程序员宅基地

文章浏览阅读2.9k次,点赞2次,收藏6次。在开发中,有时候常常需要根据用户当前的网速来做一些操作,比如图片的加载,当网速非常好的时候,比如连接的是wifi,我们就会下载高分辨率的图片,反之,当用户使用的是2g网时,我们则给他下载低分辨率的小图,从而节省用户流量。而Facebook其实已经给我们提供了这么一个库,详见network-connection-class。使用其实超级简单,先加入依赖compile 'com.f_connectionclassmanager.getinstance().getcurrentbandwidthquality 无效

20180601_Eclipse自动生成mybatis映射文件-程序员宅基地

文章浏览阅读215次。Eclipse自动生成mybatis映射文件1.安装MyBatis Generator插件打开Eclipse,找到Help--Eclipse Marketplace。搜索MyBatis Generator,Install安装即可。2.新建generatorConfig.xml文件,填写配置信息generatorConfig.xml ..._eclipse生成mybatis映射文件

.settings下莫名的多了些org.eclipse.jdt.core.prefs...-程序员宅基地

文章浏览阅读1.9k次。Eclipse的工程下.settings隐藏目录多了些文件:.org.eclipse.jdt.core.prefs,eclipse.preferences.version=1org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5org.eclipse.jdt.core.compiler.compliance=1.5org._org.eclipse.jdt.core.prefs

Vue 父子组件自定义事件传参-程序员宅基地

文章浏览阅读945次。在子组件中通过 $emit 向父组件传值,父组件除了接收传过来的值,还需要添加额外的自定义参数时demo地址:github.com/zengkun/vue…1、子组件传出单个参数时:// 子组件this.$emit('test', param)// 父组件@test='test($event, otherParams…)'复制代码2、子组件传出多个参数时:// 子组件this.$..._vue父组件自定义事件回调传多个参数

.bat文件在window下执行,只执行第一段cmd命令后就退出,不往下执行_windows 脚本 多行语句只执行第一行-程序员宅基地

文章浏览阅读8.4k次。.bat文件在window下执行,只执行第一段cmd命令后就退出,不往下执行有些时候我们会有这样的需求,多条cmd命令需要执行,想要偷懒的时候,可以把cmd命令统一copy到一个.bat文件里,然后执行。但这种.bat文件点击运行只会执行第一行cmd命令后就退出,上网查找资料大部分说的是改.bat的文件。那如果我偏要不改.bat文件,文件格式如下,要怎么一次性执行完呢?npm install ..._windows 脚本 多行语句只执行第一行

sklearn构建stacking模型进行堆叠多模型分层级回归分析_sklearn stacking-程序员宅基地

文章浏览阅读2.4k次。sklearn构建stacking模型进行堆叠多模型分层级回归分析Stacking 就是当用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集,来学习一个新的学习器。Stacking 的基础层通常包括不同的学习算法,因此stacking ensemble往往是异构的。stacking的步骤:假设有1000个样本,70%的样本作为训练集,30%的样本作为测试集。STEP1:在训练集上采用算法A、B、C等训练出一系列基学习器。STEP2:用这些基学习器的输出结果._sklearn stacking

随便推点

syzkaller配置小结_libncurses5-dev : depends: libtinfo5 (= 5.9+201401-程序员宅基地

文章浏览阅读1.9k次。记录syzkaller的配置过程及遇到的一些问题_libncurses5-dev : depends: libtinfo5 (= 5.9+20140118-1ubuntu1) but 6.1-1ubun

tomcat中的quartz定时任务每次都被执行了两次_tomacat quartz的cron表达会触发两次-程序员宅基地

文章浏览阅读8.4k次,点赞5次,收藏3次。这两天发现部署到tomcat中的quartz定时任务每回都被执行了两次,但是在myeclipse执行时又不会,后来搜了网上,才发现该问题只发生于部署在tomcat服务器上,由tomcat的自启动导致。导致该问题的原因是你的tomcat的conf目录中的server.xml有如下配置 其中第一行告诉tomcat,在启动的时候加载webapps下的所有项目工程_tomacat quartz的cron表达会触发两次

Android 第三方桌面,怎么请求Widget的android.permission.BIND_APPWIDGET_android.permission.bind_appwidget,androi-程序员宅基地

文章浏览阅读73次。在Launcher默认加载Widget时,这里会请求权限。如果我的桌面是作为第三方应用安装的。就无法获取此权限。所以无法正确加载到桌面上。 有没有大神,有什么版本让第三的应用也能获取到权限。_android.permission.bind_appwidget,androi

如何免费在线听周杰伦的歌曲_music.sonimei-程序员宅基地

文章浏览阅读8.9k次,点赞7次,收藏12次。我平常用网易云来听音乐 http://music.163.com/ ,网易云除了歌好听,评论更有故事。之前已经写过网易云音乐了,有兴趣看看之前的文章那些你可能不知道的网易云音乐奇技淫巧可惜周杰伦的大部分歌曲网易没有版权,还好我保存了一份杰伦所有的歌曲,如果你也想收藏的话公众号回复 周杰伦 获取。那首评论过百万的神曲《晴天》已经变成灰色。https://music.163.com/#/son..._music.sonimei

什么是敏捷开发流程_敏捷feature开发流程-程序员宅基地

文章浏览阅读1.6w次,点赞17次,收藏114次。这里是修真院后端小课堂,每篇分享文从【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】八个方面深度解析后端知识/技能,本篇分享的是:【什么是敏捷开发流程 】这个词猛一听起来感觉很高大上,其实现在已经是主流的团队开发流程 了。一. 先说一下官方的定义:敏捷不是指某一种具体的方法论、过程或框架,而是一组价值观和原则。符合敏捷价值观..._敏捷feature开发流程

google浏览器打不开网页解决办法,返回ERR_TUNNEL_CONNECTION_FAILED“_err_tunnel_connection_failed csdn-程序员宅基地

文章浏览阅读8.4k次,点赞4次,收藏8次。google浏览器打不开网页解决办法,而且显示:“网页可能暂时无法连接,或者它已永久性地移动到了新网址,返回ERR_TUNNEL_CONNECTION_FAILED”打开cmd:输入:ipconfig /flushdns亲测刷新一下dns配置就可以访问了,如果还不行在一次输入下面的指令后,重启电脑就可以了,但多数情况win10应该都不用重启nbtstat –rnetsh int ip resetnetsh winsock reset..._err_tunnel_connection_failed csdn

推荐文章

热门文章

相关标签