欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

并发编程-03线程安全性之原子性(Atomic包)及原理分析

发布时间:2025/3/21 编程问答 37 豆豆
生活随笔 收集整理的这篇文章主要介绍了 并发编程-03线程安全性之原子性(Atomic包)及原理分析 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

文章目录

  • 线程安全性文章索引
  • 脑图
  • 线程安全性的定义
  • 线程安全性的体现
  • 原子性
    • 使用AtomicInteger改造线程不安全的变量
    • incrementAndGet源码分析-UnSafe类 compareAndSwapInt (CAS)
    • CAS操作中可能会带来的ABA问题
      • ABA问题的解决办法
      • AtomicLong 和 LongAdder
      • LongAdder的优化思路
      • LongAdder的优缺点
    • AtomicReference 和 AtomicIntegerFieldUpdater
  • 代码

线程安全性文章索引

并发编程-03线程安全性之原子性(Atomic包)及原理分析

并发编程-04线程安全性之原子性Atomic包的4种类型详解

并发编程-05线程安全性之原子性【锁之synchronized】

并发编程-06线程安全性之可见性 (synchronized + volatile)

并发编程-07线程安全性之有序性


脑图


线程安全性的定义

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


线程安全性的体现

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

1. 原子性
2. 可见性
3. 有序性

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

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

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

我们逐个来看下这3个特性,首先来学习下线程安全的原子性JDK中提供的类以及原理。


原子性

提到原子性,就不得不提jdk1.5开始提供的juc中的Atomic包,Atomic包中的原子操作类提供了一种用法简单、性能高效、线程安全的更新一个变量的方式。

先回顾下线程不安全的写法


使用AtomicInteger改造线程不安全的变量

下面我们通过示例来演示下Atomic包中的原子类是如何线程安全的更新一个变量的方式


incrementAndGet源码分析-UnSafe类 compareAndSwapInt (CAS)

AtomicInteger#incrementAndGet 是如何实现线程安全的呢?

看下源码 (JDK1.8

调用了Unsafe类中的getAndAddInt()方法,该方法执行一个CAS操作,保证线程安全

UnSafe的getAndAddInt方法实现:

//Unsafe类中的getAndAddInt方法 public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }

getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法。 【CAS操作是基于系统原语的(原语的执行必须是连续的,操作期间不会被系统中断,是一条CPU的原子指令),因此是一个不需要加锁的锁,也因此不可能出现死锁的情况。】

CAS方法的主要实现逻辑

CAS:Compare and Swap
CAS(V, E, N)

V:要更新的变量
E:预期值
N:新值

如果V值等于E值,则将V值设为N值;如果V值不等于E值,说明其他线程做了更新,那么当前线程什么也不做。(放弃操作或重新读取数据)

在JDK中的实现为,加入了个偏移量offset

Unsafe里的CAS 操作相关:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值, //expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。 public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

上面的incrementAndGet 源码分析是基于JDK1.8的,如果是1.8之前的方法,1.8之前的方法则是通过for的死循环实现的:

//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法, //JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可 public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }

CAS操作中可能会带来的ABA问题

当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值。


ABA问题的解决办法

  • AtomicStampedReference类 时间戳

一个带有时间戳的对象引用,每次修改时,不但会设置新的值,还会记录修改时间。在下一次更新时,不但会对比当前值和期望值,还会对比当前时间和期望值对应的修改时间,只有二者都相同,才会做出更新。

底层实现为:一个键值对Pair存储数据和时间戳,并构造volatile修饰的私有实例;两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换


  • AtomicMarkableReference类

一个boolean值的标识,true和false两种切换状态表示是否被修改。不推荐使用。

原理参考:Java中的CAS和Unsafe类


AtomicLong 和 LongAdder

上面的AtomicInteger也可以改成AtomicLong ,其他地方都无需调整,效果是一样的。 这里我们要引出的JDK8中对AtomicLong的改进类LongAdder.

多次执行,结果总是10000.


LongAdder的优化思路

LongAdder所使用的思想就是热点分离,这一点可以类比一下ConcurrentHashMap的设计思想。就是将value值分离成一个数组,当多线程访问时,通过hash算法映射到其中的一个数字进行计数。而最终的结果,就是这些数组的求和累加。这样一来,就减小了锁的粒度


LongAdder的优缺点

优点:

  • LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。 如果仅仅是需要做形如count++的操作,如果使用的JDK8的话,推荐使用LongAdder代替AtomicLong。

缺点:

  • LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

AtomicReference 和 AtomicIntegerFieldUpdater

因篇幅原因 AtomicReference 和 AtomicIntegerFieldUpdater 的使用见另外一篇博客
并发编程-04线程安全性之原子性Atomic包详解

原子更新引用类型: AtomicReference

原子更新字段类型: AtomicIntegerFieldUpdater


代码

https://github.com/yangshangwei/ConcurrencyMaster

总结

以上是生活随笔为你收集整理的并发编程-03线程安全性之原子性(Atomic包)及原理分析的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。