面试官:阐述Atomic原子类的实现原理
发布时间:2021-06-08 18:11:36 所属栏目:安全 来源:互联网
导读:线程安全真的是线程的安全吗? 什么是 Atomic? 实现一个计数器 AtomicInteger 源码分析 AtomicLong 和 LongAdder 谁更牛? 总结 当我们谈论『线程安全』的时候,肯定都会想到 Atomic 类。不错,Atomic 相关类都是线程安全的,在讲 Atomic 类之前我想再聊聊『线
线程安全真的是线程的安全吗?
什么是 Atomic?
实现一个计数器
AtomicInteger 源码分析
AtomicLong 和 LongAdder 谁更牛?
总结
当我们谈论『线程安全』的时候,肯定都会想到 Atomic 类。不错,Atomic 相关类都是线程安全的,在讲 Atomic 类之前我想再聊聊『线程安全』这个概念。
线程安全真的是线程的安全吗?
初看『线程安全』这几个字,很容易望文生义,这不就是线程的安全吗?其实不是,线程本身没有好坏,没有『安全的线程』和『不安全的线程』之分,俗话说:人之初性本善,线程天生也是纯洁善良的,真正让线程变坏是因为访问的变量的原因,变量对于操作系统来说其实就是内存块,所以绕了这么一大圈,线程安全称为『内存的安全』可能更为贴切。
简而言之,线程访问的内存决定了这个线程是否是安全的。
变量大致可以分为局部变量和共享变量,局部变量对于 JVM 来说是栈空间,大家都背过八股文,栈是线程私有的是非共享的,那自然也是内存安全的;共享变量对于 JVM 来说一般是存在于堆上,堆上的东西是所有线程共享的,如果不加任何限制自然是不安全的。
因为线程安全这个概念已经深入人心了,所以后面我们还是用线程安全来表达内存安全的含义。
那如何解决这种不安全呢?方法有很多,比如:加锁、Atomic 原子类等。
好了,咱们今天先来看看Atomic类。
什么是 Atomic?
Java从JDK1.5开始提供java.util.concurrent.atomic包,这里包含了多个原子操作类。原子操作类提供了一个简单、高效、安全的方式去更新一个变量。
Atomic 包下的原子操作类有很多,可以大致分为四种类型:
原子操作基本类型
原子操作数组类型
原子操作引用类型
原子操作更新属性
Atomic原子操作类在源码中都使用了Unsafe类,Unsafe类提供了硬件级别的原子操作,可以安全地直接操作内存变量。后面讲解源码时再详细介绍。
实现一个计数器
假如在业务代码中需要实现一个计数器的功能,啪地一下,很快我们就写出了以下的代码:
public class Counter {
private int count;
public void increase() {
count++;
}
}
increase方法对 count 变量进行递增。
当代码提交上库进行code review时,啪地一下,很快收到了检视意见(严重级别):
如果在多线程场景下,你的计数器可能有问题。
上大一的时候老师就讲过 count++ 是非原子性的,它实际上包含了三个操作:读数据,加一,写回数据。
再次修改代码,多线访问increase方法会有问题,那就给它加个锁吧,count变量修改了其他线程可能不能即时看到,那就给变量加个 volatile 吧。
吭哧吭哧,代码如下:
public class LockCounter {
private volatile int count;
public synchronized void increase() {
count++;
}
}
一顿操作猛如虎,再次提交代码后,依然收到了检视意见(建议级别):
加锁会影响效率,可以考虑使用原子操作类。
原子操作类?「黑人问号脸」,莫不是大佬知道我晚上有约会故意整我,不想合入代码吧。带着将信将疑的态度,打开百度谷歌,原来 AtomicInteger 可以轻松解决这个问题,手忙脚乱一顿复制粘贴代码搞定了,终于可以下班了。
public class 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:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
value 变量保存当前对象的值,valueOffset 是变量的内存偏移地址,也是通过调用unsafe的方法获取。
public final class Unsafe {
// ……省略其他方法
public native long objectFieldOffset(Field f);
}
这里再说说 Unsafe 这个类,人如其名:不安全的类。打开 Unsafe 类会看到大部分方法都标识了 native,也就是说这些都是本地方法,本地方法强依赖于操作系统平台,一般都是采用C/C++语言编写,在调用 Unsafe 类的本地方法实际会执行这些方法,熟悉 C/C++的小伙伴可自行下载源码研究。
![]() (编辑:阜新站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |