竹笋

首页 » 问答 » 灌水 » 面试官说说Atomic原子类的实现原理
TUhjnbcbe - 2024/4/5 23:25:00

线程安全真的是线程的安全吗?什么是Atomic?实现一个计数器AtomicInteger源码分析AtomicLong和LongAdder谁更牛?总结

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

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

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

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

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

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

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

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

什么是Atomic?

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

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

原子操作基本类型原子操作数组类型原子操作引用类型原子操作更新属性

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

实现一个计数器

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

publicclassCounter{privateintcount;publicvoidincrease(){count++;}}

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

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

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

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

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

吭哧吭哧,代码如下:

publicclassLockCounter{privatevolatileintcount;publicsynchronizedvoidincrease(){count++;}}

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

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

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

publicclassAtomicCounter{privateAtomicIntegercount=newAtomicInteger(0);publicvoidincrease(){count.incrementAndGet();}}AtomicInteger源码分析

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

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

/***在当前值的基础上自动加1**

return更新后的值*/publicfinalintincrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,1)+1;}

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

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

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

privatestaticfinalUnsafeunsafe=Unsafe.getUnsafe();privatestaticfinallongvalueOffset;static{try{valueOffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));}catch(Exceptionex){thrownewError(ex);}}privatevolatileintvalue;

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

publicfinalclassUnsafe{//……省略其他方法publicnativelongobjectFieldOffset(Fieldf);}

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

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

publicfinalclassUnsafe{//……省略其他方法publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{v=getIntVolatile(o,offset);//循环CAS操作}while(!

1
查看完整版本: 面试官说说Atomic原子类的实现原理