• 线程安全真的是线程的安全吗?

  • 什么是 Atomic?

  • 实现一个计数器

  • AtomicInteger 源码分析

  • AtomicLong 和 LongAdder 谁更牛?

  • 总结

当我们谈论『线程安全』的时候,肯定都会想到 Atomic 类。不错,Atomic 相关类都是线程安全的,在讲 Atomic 类之前我想再聊聊『线程安全』这个概念。

线程安全真的是线程的安全吗?

初看『线程安全』这几个字,很容易望文生义,这不就是线程的安全吗?其实不是,线程本身没有好坏,没有『安全的线程』和『不安全的线程』之分,俗话说:人之初性本善,线程天生也是纯洁善良的,真正让线程变坏是因为访问的变量的原因,变量对于操作系统来说其实就是内存块,所以绕了这么一大圈,线程安全称为『内存的安全』可能更为贴切。

简而言之,线程访问的内存决定了这个线程是否是安全的。

变量大致可以分为局部变量共享变量,局部变量对于 JVM 来说是栈空间,大家都背过八股文,栈是线程私有的是非共享的,那自然也是内存安全的;共享变量对于 JVM 来说一般是存在于堆上,堆上的东西是所有线程共享的,如果不加任何限制自然是不安全的。

因为线程安全这个概念已经深入人心了,所以后面我们还是用线程安全来表达内存安全的含义。

那如何解决这种不安全呢?方法有很多,比如:加锁、Atomic 原子类等。

好了,咱们今天先来看看Atomic类

什么是 Atomic?

JavaJDK1.5开始提供java.util.concurrent.atomic包,这里包含了多个原子操作类。原子操作类提供了一个简单、高效、安全的方式去更新一个变量。

Atomic 包下的原子操作类有很多,可以大致分为四种类型:

  • 原子操作基本类型

  • 原子操作数组类型

  • 原子操作引用类型

  • 原子操作更新属性

Atomic原子操作类在源码中都使用了Unsafe类Unsafe类提供了硬件级别的原子操作,可以安全地直接操作内存变量。后面讲解源码时再详细介绍。

实现一个计数器

假如在业务代码中需要实现一个计数器的功能,啪地一下,很快我们就写出了以下的代码:

publicclass Counter {privateint count;public void increase() {count++;}
}

increase方法对 count 变量进行递增。

当代码提交上库进行code review时,啪地一下,很快收到了检视意见(严重级别):

如果在多线程场景下,你的计数器可能有问题。

上大一的时候老师就讲过 count++ 是非原子性的,它实际上包含了三个操作:读数据,加一,写回数据。

再次修改代码,多线访问increase方法会有问题,那就给它加个锁吧,count变量修改了其他线程可能不能即时看到,那就给变量加个 volatile 吧。

吭哧吭哧,代码如下:

publicclass LockCounter {privatevolatileint count;public synchronized void increase() {count++;}
}

一顿操作猛如虎,再次提交代码后,依然收到了检视意见(建议级别):

加锁会影响效率,可以考虑使用原子操作类。

原子操作类?「黑人问号脸」,莫不是大佬知道我晚上有约会故意整我,不想合入代码吧。带着将信将疑的态度,打开百度谷歌,原来 AtomicInteger 可以轻松解决这个问题,手忙脚乱一顿复制粘贴代码搞定了,终于可以下班了。

publicclass AtomicCounter {private AtomicInteger count = new AtomicInteger(0);public void increase() {count.incrementAndGet();}
}

AtomicInteger 源码分析

调用AtomicInteger类incrementAndGet方法不用加锁可以实现安全的递增,这个好神奇,下面带领大家分析一下源码是这么实现的,等不及了等不及了。

打开源码,可以看到定义的incrementAndGet方法:

/**
* 在当前值的基础上自动加 1
*
* @return 更新后的值
*/
public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

通过源码可以看到实际上是调用了 unsafe 的一个方法,unsafe 是什么待会再说。

我们再看看getAndAddInt方法的参数:第一个参数 this 是当前对象的引用;第二个参数valueOffset是用来记录value值在内存中的偏移地址,第三个参数是一个常量 1;

在 AtomicInteger 中定义了一个常量valueOffset和一个可变的成员变量 value

privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();
privatestaticfinallong valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { thrownew Error(ex); }
}privatevolatileint value;

value 变量保存当前对象的值,valueOffset 是变量的内存偏移地址,也是通过调用unsafe的方法获取。

publicfinalclass Unsafe {// ……省略其他方法public native long objectFieldOffset(Field f);
}

这里再说说 Unsafe 这个类,人如其名:不安全的类。打开 Unsafe 类会看到大部分方法都标识了 native,也就是说这些都是本地方法,本地方法强依赖于操作系统平台,一般都是采用C/C++语言编写,在调用 Unsafe 类的本地方法实际会执行这些方法,熟悉 C/C++的小伙伴可自行下载源码研究。

好了,我们再回到最开始,调用了 Unsafe 类的getAndAddInt方法:

publicfinalclass Unsafe {// ……省略其他方法public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset); // 循环 CAS 操作} while (!compareAndSwapInt(o, offset, v, v + delta));return v;}// 根据内存偏移地址获取当前值public native int getIntVolatile(Object o, long offset);// CAS 操作public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
}

通过getIntVolatile方法获取当前 AtomicInteger 对象的value值,这是一个本地方法。

然后调用compareAndSwapInt进行 CAS 原子操作,尝试在当前值的基础上加 1,如果 CAS 失败会循环进行重试。

因此compareAndSwapInt方法是最核心的,详细实现大家可以自行找源码看。这里我们看看方法的参数,一共有四个参数:o 是指当前对象;offset 是指当前对象值的内存偏移地址;expected是期望值;x是修改后的值;

compareAndSwapInt方法的思路是拿到对象 o 和 offset 后会再去取对象实际的值,如果当前值与之前取的期望值是一致的就认为 value 没有被修改过,直接将 value 的值更新为 x,这样就完成了一次 CAS 操作,CAS 操作是通过操作系统保证原子性的。

如果当前值与期望值不一致,说明 value 值被修改过,那么就会重试 CAS 操作直到成功。

AtomicInteger类中还有很多其他的方法,如:

decrementAndGet()
getAndDecrement()
getAndIncrement()
accumulateAndGet()
// …… 省略

这些方法实现原理都是大同小异,希望大家可以举一反三理解其他的方法。

另外还有一些其他的类,如:AtomicLongAtomicReferenceAtomicIntegerArray等,这里也不再赘述,原理都是大同小异。

AtomicLong 和 LongAdder 谁更牛?

Java 在 jdk1.8版本 引入了 LongAdder 类,与 AtomicLong 一样可以实现加、减、递增、递减等线程安全操作,但是在高并发竞争非常激烈的场景下 LongAdder 的效率更胜一筹,后续单独用一篇文章进行介绍。

总结

讲了半天,可能有的小伙伴还是比较懵,Atomic 类到底是如何实现线程安全的?

在语言层面上,Atomic 类是没有做任何同步操作的,翻看源代码方法没有任何加锁,其实最大功劳还是在 CAS 身上。CAS 利用操作系统的硬件特性实现了原子性,利用 CPU 多核能力实现了硬件层面的阻塞。

只有 CAS 的原子性保证就一定是线程安全的吗?当然不是的,通过源码发现 value 变量还用了 volatile 修饰了,保证了线程可见性。

那有些小伙伴可能要问了,那是不是加锁就没有用了,非也,虽然基于 CAS 的线程安全机制很好很高效,但是这适合一些粒度比较小的需求才有效,如果遇到非常复杂的业务逻辑还是需要加锁操作的。

大家学会了吗?

通俗有趣讲解Atomic原子类的实现原理相关推荐

  1. JUC多线程:Atomic原子类与CAS原理

    一.Atomic 原子类的原理: Atomic 原子操作类是基于无锁 CAS + volatile 实现的,并且类中的所有方法都使用 final 修饰,进一步保证线程安全.而 CAS 算法的具体实现方 ...

  2. 「死磕Java并发编程」说说Java Atomic 原子类的实现原理

    <死磕 Java 并发编程>系列连载中,大家可以关注一波. 「死磕 Java 并发编程」阿里二面,面试官:说说 Java CAS 原理? 「死磕 Java 并发编程」面试官:说说什么是 J ...

  3. Atomic原子类常用方法总结(包含四大类型)

    基本介绍: Atomic指一个操作不可中断,即使在多线程情况下,一个操作一旦开始,就不会被其他线程干扰.如果多线程中仅需要Atomic原子类解决的事情,就不需要synchronized重量级锁了. 原 ...

  4. Java多线程进阶面试-Atomic 原子类

    1.介绍一下 Atomic 原子类 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的. ...

  5. 16.Atomic原子类体系概览

    老王:小陈啊,从今天开始我们就要进入Atomic原子类系列的学习了,首先啊给你看一下JDK中提供给我们使用的原子类有哪些? 小陈:好啊,我记得JUC下面提供的原子类还是挺多的,所有的原子类的使用和底层 ...

  6. atomic原子类实现机制_JUC学习笔记--Atomic原子类

    Atomic 原子操作类包 Atomic包 主要是在多线程环境下,无锁的进行原子操作.核心操作是基于UnSafe类实现的CAS方法 CAS CAS: compareAndSwap :传入两个值:期望值 ...

  7. atomic原子类实现机制_深入了解Java atomic原子类的使用方法和原理

    在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicIntege ...

  8. atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题

    本文基于JDK1.8 Atomic原子类 原子类是具有原子操作特征的类. 原子类存在于java.util.concurrent.atmic包下. 根据操作的数据类型,原子类可以分为以下几类. 基本类型 ...

  9. (转)Java atomic原子类的使用方法和原理(一)

    在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicIntege ...

  10. Atomic原子类和Unsafe魔法类 详解

    文章目录 1. Atomic原子类 1.1 Atomic原子类的作用 1.2 原子更新基本类型类 1.3 原子更新数组类 1.4 原子更新引用类型 1.5 原子更新字段类 2. Unsafe魔法类 2 ...

最新文章

  1. MVC、MVP和MVVM的优缺点
  2. CentOS7下启动Nginx出现Failed to start nginx.service:unit not found
  3. 一段处理百分数的js代码
  4. 为什么自动化对于IT工程师是必须的?
  5. jq的form data中保留加号_使用Fourier+EMD水管中传播的单频音波进行滤波(Python)...
  6. python 字符串格式化,使用f前缀
  7. [C++]有关深复制与copy constructor的一些问题与实例
  8. 学用状态机模式,写的报销流程,请指教
  9. 挖掘建模-分类与预测-决策树
  10. python包裹和运费_这个Python库真的太好用了,10行代码就能轻松搞定目标检测
  11. Java后端学习路线(校招前准备)
  12. EMNLP2021 | 标签推理的细粒度实体识别
  13. 21天通关python 磁力_利用python爬取天天看美剧磁力链接
  14. Excel: 批量去除空格的函数——trim函数, substitute函数,clean函数
  15. ALK/NH2/COOH/NHS/N3/hydrazide/maleimide/Tetrazine/DBCO/寡聚物等基团修饰BODIPY630/650氟硼荧
  16. QT-数据可视化大屏1
  17. 积木报表VS睿思BI报表
  18. word文档中把几个图形组合在一起
  19. 暑假训练 The Triangle Game (OpenJ_Bailian - 1574)
  20. 利用微软Text-To-Speech朗读文本

热门文章

  1. CCNA培训课总结笔记--静态路由实现负载均衡(四)
  2. string字符串比较
  3. java新增mysql时 中文出现_Java项目往数据库中插入数据,出现中文乱码
  4. mybatis将字段改为null_【MyBatis入门到入土精讲】MyBatis介绍
  5. 无法访问_win10纯净版提示无法访问文件或目录损坏的问题
  6. win7工作组无法查看计算机名,win7系统无法查看工作组计算机怎么解决
  7. 软件工程中逻辑覆盖的例题_干货丨一文读懂:飞算全自动软件工程平台如何提升软件开发效率...
  8. Mysql之数据库与sql
  9. java awt 教程_JAVA教程第五讲AWT图形用户界面设计
  10. 【LA3942】Remember the World(初识前缀树Trie----模版题 + dp)