线程操作原子性

本文探讨了Java中线程操作原子性的重要性,通过违反原子性的示例解释了并发执行i++可能出现的问题,并介绍了如何使用同步锁、CAS(Compare and Swap)以及Atomic工具类来解决这类问题。此外,还讨论了CAS的ABA问题及其解决方案,并列举了Java 1.8中为提高高并发性能而引入的更新器和计数器。
摘要由CSDN通过智能技术生成

原子性

违反原子性示例

public  class Counter{
    volatile int i=0;
    public void add(){
        i++;
    }
    public static void main (String[]args)throws InterruptedExcetion{
        final Counter ct=new Counter();
        for(int i=0;i<6;i++){
            new Thread(new Runnable(){
                public void run(){
                    for(int j=0;j<10000;j++){
                        ct.add();
                    }
                }
            });
        }
        Thread.sleep(6000L);
        System.out.println(ct.i);
    }
}

上面代码是六个线程,每个线程执行一万遍i++;结果会小于6万,出现这种用的原因是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8KaOzvaW-1642326593364)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211215220044046.png)]

如上图所示,t1和t2进行i++操作,i++通过javap编译后看出经过getfileld(从主内存获取变量值)、iconst_1(入栈)、iadd、putfield(同步到主内存)等操作,t1和t2从主内存第一次获取i的初始值都是0,假如t1线程优先被执行,经过i++操作,i变成1写入到主内存,此时t2工作内存中的i还是之前的初始值0,经过i++后,t2将i=1同步到主内存,此时t2执行的i++相当于失效,所有i++6万次,可能有很多这样的失效操作,导致最后结果小于6万

解决方案

  1. 可以加入同步锁synchronized、Lock,这样的缺点因为是同步的,同时只支持一个并发
  2. 使用CAS实现的Automic工具类

CAS

概念

Compare and swap 比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先对旧值进行比较,若没有发生变化,才交换成新值,发生了变化则不交换。

CAS解决i++问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4IyfYY1-1642326593367)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211215225551532.png)]

违反原子性示例的问题通过CAS操作进行解决:

线程进行相加操作,同步到主内存前没有进行CAS 对比操作,比如修改i的变量值同步到主内存前会将修改前的值和主内存中i的值进行对比,如果相等则将修改后的值1同步到主内存,这是t2将相加后的的值1(0,1)同步到主内存,这时经过对比t2线程的i旧值0和主内存中i的值1不相等,所以同步失败,此时t2会进入自悬状态,重新从主内存中加载 i 的值,然后i++操作,然后进行对比操作,到同步到主内存成功为止

CAS的三个问题

  1. 循环+CAS,自旋的实现让所有线程都处于高频运行(runable状态),争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源浪费
  2. 仅针对单个变量的操作,不能用于多个变量来实现原子操作
  3. ABA问题

ABA问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-04jPepVY-1642326593368)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211219122457972.png)]

如上图所示:

thread1、thread2同时读取到i=0后;thread1、thread2都要执行CAS(0,1)操作;假设thread2操作稍后于thread1,

则thread1执行成功;thread1紧接着执行了CAS(1,0),将i的值改回0,这样thread2执行CAS(0,1)预期是失败的,但是可以执行成功。

该问题可以通过版本号来解决,如使用AtomicStampedReference类

原子操作封装类

  1. AtomicBoolean :原子更新布尔类型
  2. AtomicInteger: 原子更新整型
  3. AtomicLong: 原子更新长整型
  4. AtomicIntegerArray: 原子更新整型数组里的元素
  5. AtomicLongArray: 原子更新长整型数组里的元素
  6. AtomicReferenceArray: 原子更新引用类型数组里的元素
  7. AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
  8. AtomicStampedReference: 原子更新带有版本号的引用类型。
  9. AtomicStampedReference: 原子更新带有版本号的引用类型**(有效防止ABA问题)**

1.8更新

计数器增强版,高并发下性能更好

更新器: DoulbeAccumulator、LongAccumulator

计数器: DoubleAdder、LongAdder

原理: 分成多个操作单元,不同线程更新不同的单元,只需要汇总的时候才计算所有的单元操作。

场景: 高并发频繁更新、不太频繁地读取。

导致线程安全的原因

  1. 可见性
  2. 安全性
    分成多个操作单元,不同线程更新不同的单元,只需要汇总的时候才计算所有的单元操作。

场景: 高并发频繁更新、不太频繁地读取。

导致线程安全的原因

  1. 可见性
  2. 安全性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值