多线程和并发编程(2)—CAS和Atomic实现的非阻塞同步

news2024/12/23 19:52:09

在并发编程中实现原子操作可以使用锁,锁机制满足基本的需求是没有问题的了,但是有的时候我们的需求并非这么简单,我们需要更有效,更加灵活的机制,synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁。

这里会有些问题,首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得锁的线程一直不释放锁怎么办?同时,还有可能出现一些例如死锁之类的情况,最后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重。为了解决这个问题,Java提供了Atomic系列的原子操作类。

image-20230912100455360

一、CAS的原理

CAS(compare and swap)是一种非阻塞同步的实现方式,如其名字含义,他的核心思想就是先比较再替换,在AtomicInteger和其他原子操作的工具类中运用的比较多。

核心思路是比较三个值,CAS(V,E,N),其中V是要更新的值的地址,E是预期查询出来的值,N是更新后的值,只有当要更新的V查到的值和预期值E一致的时候,才能正常更新到N。他的底层是利用Unsafe类来进行操作,该类是对内存进行直接操作,保障指令的原子性,主要是执行native方法compareAndSwapObject()、compareAndSwapInt()、compareAndSwapLong()等方法。

//Unsafe.java

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

二、Atomic原子操作类

1.基本类型

AtomicInteger

主要提供基本数据类型包装类的原子操作:

  1. int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
  2. boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  3. int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
  4. int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

AtomicIntegerArray

主要是提供原子的方式更新数组里的整型,其常用方法如下。

  1. int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
  2. boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
  3. 需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

2.引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类。

AtomicReference

原子更新引用类型。

AtomicStampedReference

利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了。这就是AtomicStampedReference的解决方案。AtomicMarkableReference跟AtomicStampedReference差不多, AtomicStampedReference是使用pair的int stamp作为计数器使用,AtomicMarkableReference的pair使用的是boolean mark。 还是那个水的例子,AtomicStampedReference可能关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过,方法都比较简单。

AtomicMarkableReference:

原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,booleaninitialMark)。

3.AtomicInteger详解

以下详细说明AtomicInteger的使用:

常见方法:

addAndGet()// 以原子方式将给定值添加到当前值,并在添加后返回新值。
getAndAdd() // 以原子方式将给定值添加到当前值并返回旧值。
incrementAndGet()// 以原子方式将当前值递增1并在递增后返回新值。它相当于i ++操作。
getAndIncrement() // 以原子方式递增当前值并返回旧值。它相当于++ i操作。
decrementAndGet()// 原子地将当前值减1并在减量后返回新值。它等同于i-操作。
getAndDecrement() // 以原子方式递减当前值并返回旧值。它相当于-i操作。
boolean compareAndSet(int expect, int update) //比较和交换操作将内存位置的内容与给定值进行比较,并且只有它们相同时,才将该内存位置的内容修改为给定的新值

代码示例:

public class AtomicTest {
    public static void main(String[] args) {
        AtomicInteger atomic = new AtomicInteger(0);
        int i = atomic.addAndGet(3);
        System.out.println("i = " + i);

        int i1 = atomic.getAndAdd(5);
        System.out.println("i1 = " + i1);

        int i2 = atomic.incrementAndGet();
        System.out.println("i2 = " + i2);

        int i3 = atomic.getAndIncrement();
        System.out.println("i3 = " + i3);

        int i4 = atomic.getAndDecrement();
        System.out.println("i4 = " + i4);

        int i5 = atomic.decrementAndGet();
        System.out.println("i5 = " + i5);


        //1、默认初始值
        AtomicInteger atomicInteger = new AtomicInteger(100);
        //2、默认初始值和给定值,都是100,所以会更改成功
        boolean isSuccess = atomicInteger.compareAndSet(100,110);   //current value 100
        //3、返回true
        System.out.println(isSuccess);      //true
        //4、默认初始值是110,给定值是100,所以会更改失败
        isSuccess = atomicInteger.compareAndSet(100,120);       //current value 110
        //5、返回false
        System.out.println(isSuccess);      //false
    }
}

三、CAS实现原子性面临的问题

1.ABA问题

ABA问题是当我查询到V所对应的值A后,又有其他线程将该值改为B,后又改回A的情况,这种情况对于CAS操作来说是分辨不出来的。一种常见解决类似ABA的问题的思路是加入了version字段用来标识每次更新的版本信息,如果更新完成之后version发生了变更,就表明被其他线程进行了更改。在CAS中,是通过Atomic类下的AtomicStampedReference解决了ABA的问题,是通过加入了时间戳(stamp)来区分是否中途有更改,获取到新值之后,再通过时间戳比较是否中途被其他线程修改过。

//AtomicStampedReference.java 源码

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);
        }
    }

    private volatile Pair<V> pair;
  //...
}

2.循环时间长开销大。

自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

针对并发处理时间较长的场景,可以考虑使用synchronize进行加锁操作,竞争共享资源的线程在资源竞争不到的时候是进入阻塞状态,不会一直占用CPU,相比CAS自旋更合适。

3.只能保证一个共享变量的原子操作。

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。

解决办法就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

四、总结

CAS是一种非阻塞同步方法处理并发问题,相较于synchronize加锁操作,其操作上更轻量级,不会阻塞线程,但针对并发量很大并且竞争非常激烈的场景,可能使用CAS就不太合适,由于竞争到锁的概率很低,这就会造成CPU空转,导致资源浪费。

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零代码编程:用ChatGPT来批量删除特定文件

一个文件夹中有很多个文件重复了&#xff0c;重复的文件中都含有“&#xff08;1&#xff09;”这样的字符&#xff0c;需要把所有这些文件批量删除掉。 在ChatGPT中输入如下提示词&#xff1a; 你是一个Python编程专家&#xff0c;写一段代码完成批量删除文件的任务&#xff…

【java】【SSM框架系列】【四】SpringBoot

目录 一、SpringBoot简介 1.1 入门案例 1.1.1 案例 1.1.2 Spring程序与SpringBoot区别 1.1.3 SpringBoot项目快速启动 1.2 SpringBoot概述 二、基础配置 2.1 配置文件格式 2.1.1 配置文件格式&#xff08;3种&#xff09; 2.1.2 配置文件间的加载优先级&#xff08;了解…

电视机顶盒哪个牌子好?拆机达人盘点网络电视机顶盒排名

电视机顶盒哪个牌子好&#xff1f;在挑选电视机顶盒的时候&#xff0c;我们要注意的是盒子的芯片、内存以及系统和操作等等方面全都要衡量&#xff0c;根据我多年拆机经验来说&#xff0c;有些产品存在虚标配置、偷工减料等情况&#xff0c;在选购时不懂行可以参考以下网络电视…

【mysql】—— 函数的基本介绍

前言&#xff1a; MySQL是一种常用的关系型数据库管理系统&#xff0c;它提供了许多内置的函数来进行数据操作和处理。本期&#xff0c;我将给大家介绍的就是关于 “函数” 的相关知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;日期函数 &#…

调整Pycharm中代码的字体的大小

注意&#xff1a;是代码的字体大小&#xff0c;不是Pycharm标题栏、状态栏啥的字体的大小。 1、第一步 2、第二步&#xff0c;勾选“用ctrl滚轮”调整字体大小 3、在代码框中&#xff0c;ctrl鼠标滚轮就能调节代码字体大小了。

《程序员职场工具库》认识 OGSM 模型

最近简单学习了一下 OGSM 模型&#xff0c;把一些学习感想给大家分享一下。可能我们用不到这个模型&#xff0c;但是也算是一个小知识点&#xff0c;留个印象也不错。 OGSM 模型是企业战略管理的理论工具。它主要是以下 4 个部分&#xff1a; O&#xff08;Objective&#xf…

ChromeDriver最新版(116.x及最新)下载途径分享

首先查看version&#xff1a;在地址栏输入chrome://version/ 最新版 官方地址&#xff1a;https://googlechromelabs.github.io/chrome-for-testing/ 选择合适的下载即可 Old 官网&#xff1a;https://chromedriver.chromium.org/downloads 国内镜像 ps:国内镜像没有…

基于C#的图书管理系统数据库设计报告

第一章 问题描述 1.1 图书管理系统简介 本系统利用.NET处理数据库的功能&#xff0c;实现对图书馆信息的管理。主要功能为管理有关读者、出版社、书籍、借阅和管理者的信息等。 本系统的结构分为读者信息管理模块、出版社信息管理模块、书籍信息管理模块、借阅信息管理模块、…

腾讯云轻量应用服务器详细介绍_轻量值得买吗?

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别优惠&#xff0c;轻量2核2G3M带宽95元一年、2核2G4M优惠价112元一年&#xff0c;396元三年、2核4G5M带宽168元一年&#xff0c;628元3年、4核8G12M带宽446元一年&#xff0c;…

面经pc端项目

创建项目 安装脚手架-----创建项目------选择自定义 sass基础语法 https://www.sass.hk/ sass语法有两个:sass(旧) scss(新) 1.scss语法 和less语法类似,支持嵌套,支持变量… scss: $变量名 less: @变量名 $color:orange; .box{width: 400px;height: 400px;borde…

Linux 修改SSH的显示样式,修改终端shell显示的样式,美观更改

要修改SSH的显示样式&#xff0c;您可以使用自定义的PS1&#xff08;提示字符串1&#xff09;变量来更改命令行提示符的外观。在您的情况下&#xff0c;您想要的格式似乎包括日期和时间&#xff0c;以及当前目录。以下是一个示例PS1设置&#xff0c;可以实现您所描述的样式&…

LVS -DR

一、DR模式数据包流向分析 1.Client 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源IP是 CIP&#xff0c;目的IP 是VIP&#xff09;到达内核空间。 2.Director Server&#xff08;负载均衡器&#xff09;和 R…

【猿灰灰赠书活动 - 06期】- 【计算机考研书单——408专属】

&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;博文为大家争取福利&#xff0c;与机械工业出版社合作进行送书活动 &#x1f468;‍&#x1f…

【软件测试】设计优秀的测试用例

前言 我从来没有好好的写过一个测试用例&#xff0c;之前做开发虽然写单元测试和流程测试&#xff0c;基本上都是基于自己的代码&#xff0c;而且单元测试和流程测试的框和规范已经非常完善&#xff0c;你只需要填空就行&#xff0c;后来转做自动化测试&#xff0c;但我的做事…

Python入门教程 | Python 函数与参数

函数简介 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。 函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这…

【数据结构】 Map和Set详解

文章目录 &#x1f340;Map与Set的概念及场景&#x1f333;Map与Set模型介绍&#x1f3a8;Map 的使用&#x1f4cc;Map说明&#x1f4cc;Map.Entry<K, V>的说明&#x1f4cc;Map 的常用方法说明&#x1f6a8;注意事项&#x1f6a9;TreeSet的使用 &#x1f38b;Set 的说明…

无涯教程-JavaScript - IPMT函数

描述 IPMT函数根据定期,固定的还款额和固定的利率返回给定投资期限内的利息支付。 语法 IPMT (rate, per, nper, pv, [fv], [type])争论 Argument描述Required/OptionalRateThe interest rate per period.RequiredPerThe period for which you want to find the interest a…

Linux安装Redis(详细教程)

Linux安装Redis 注&#xff1a;希望将redis安装到此目录 /usr/local/redis 希望将安装包下载到此目录 /usr/local/src 可自己选择 1.创建安装目录/usr/local/redis mkdir /usr/local/redis 2.进入安装包目录 cd /usr/local/redis 3.进行下载安装包 wget https://download…

多重数据保障,数据安全可靠!移动云云数据库 my SQL成为多行业“守护者”

投入高、周期长、不灵活、持续运维压力大、既费时又耗钱&#xff0e;&#xff0e;&#xff0e;&#xff0e;&#xff0e;传统自建数据库基于传统开发模式的限制&#xff0c;存在很多使用的痛点和不足。而移动云的云数据库mySQL&#xff0c;可以说通过自己的优势一一击破了这些使…

uniapp-地区的四级联动

本来填写订单的页面选地址是三级联动的 但是由于领导的要求&#xff0c;需要改成四级联动 解决思路 最开始用的是官方的 picker , 所以我去翻看了uniapp 的官网 我们需要用到的是多列模式 解决步骤 1. 先封装对应的请求 /*** 获取省市县街道的列表*/ export const getA…