新聞中心
- 線程安全真的是線程的安全嗎?
- 什么是 Atomic?
- 實(shí)現(xiàn)一個(gè)計(jì)數(shù)器
- AtomicInteger 源碼分析
- AtomicLong 和 LongAdder 誰更牛?
- 總結(jié)
當(dāng)我們談?wù)摗壕€程安全』的時(shí)候,肯定都會(huì)想到 Atomic 類。不錯(cuò),Atomic 相關(guān)類都是線程安全的,在講 Atomic 類之前我想再聊聊『線程安全』這個(gè)概念。

10余年的織金網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都營(yíng)銷網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整織金建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“織金網(wǎng)站設(shè)計(jì)”,“織金網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
線程安全真的是線程的安全嗎?
初看『線程安全』這幾個(gè)字,很容易望文生義,這不就是線程的安全嗎?其實(shí)不是,線程本身沒有好壞,沒有『安全的線程』和『不安全的線程』之分,俗話說:人之初性本善,線程天生也是純潔善良的,真正讓線程變壞是因?yàn)樵L問的變量的原因,變量對(duì)于操作系統(tǒng)來說其實(shí)就是內(nèi)存塊,所以繞了這么一大圈,線程安全稱為『內(nèi)存的安全』可能更為貼切。
簡(jiǎn)而言之,線程訪問的內(nèi)存決定了這個(gè)線程是否是安全的。
變量大致可以分為局部變量和共享變量,局部變量對(duì)于 JVM 來說是??臻g,大家都背過八股文,棧是線程私有的是非共享的,那自然也是內(nèi)存安全的;共享變量對(duì)于 JVM 來說一般是存在于堆上,堆上的東西是所有線程共享的,如果不加任何限制自然是不安全的。
因?yàn)榫€程安全這個(gè)概念已經(jīng)深入人心了,所以后面我們還是用線程安全來表達(dá)內(nèi)存安全的含義。
那如何解決這種不安全呢?方法有很多,比如:加鎖、Atomic 原子類等。
好了,咱們今天先來看看Atomic類。
什么是 Atomic?
Java從JDK1.5開始提供java.util.concurrent.atomic包,這里包含了多個(gè)原子操作類。原子操作類提供了一個(gè)簡(jiǎn)單、高效、安全的方式去更新一個(gè)變量。
Atomic 包下的原子操作類有很多,可以大致分為四種類型:
- 原子操作基本類型
- 原子操作數(shù)組類型
- 原子操作引用類型
- 原子操作更新屬性
Atomic原子操作類在源碼中都使用了Unsafe類,Unsafe類提供了硬件級(jí)別的原子操作,可以安全地直接操作內(nèi)存變量。后面講解源碼時(shí)再詳細(xì)介紹。
實(shí)現(xiàn)一個(gè)計(jì)數(shù)器
假如在業(yè)務(wù)代碼中需要實(shí)現(xiàn)一個(gè)計(jì)數(shù)器的功能,啪地一下,很快我們就寫出了以下的代碼:
- public class Counter {
- private int count;
- public void increase() {
- count++;
- }
- }
increase方法對(duì) count 變量進(jìn)行遞增。
當(dāng)代碼提交上庫(kù)進(jìn)行code review時(shí),啪地一下,很快收到了檢視意見(嚴(yán)重級(jí)別):
如果在多線程場(chǎng)景下,你的計(jì)數(shù)器可能有問題。
上大一的時(shí)候老師就講過 count++ 是非原子性的,它實(shí)際上包含了三個(gè)操作:讀數(shù)據(jù),加一,寫回?cái)?shù)據(jù)。
再次修改代碼,多線訪問increase方法會(huì)有問題,那就給它加個(gè)鎖吧,count變量修改了其他線程可能不能即時(shí)看到,那就給變量加個(gè) volatile 吧。
吭哧吭哧,代碼如下:
- public class LockCounter {
- private volatile int count;
- public synchronized void increase() {
- count++;
- }
- }
一頓操作猛如虎,再次提交代碼后,依然收到了檢視意見(建議級(jí)別):
加鎖會(huì)影響效率,可以考慮使用原子操作類。
原子操作類?「黑人問號(hào)臉」,莫不是大佬知道我晚上有約會(huì)故意整我,不想合入代碼吧。帶著將信將疑的態(tài)度,打開百度谷歌,原來 AtomicInteger 可以輕松解決這個(gè)問題,手忙腳亂一頓復(fù)制粘貼代碼搞定了,終于可以下班了。
- public class AtomicCounter {
- private AtomicInteger count = new AtomicInteger(0);
- public void increase() {
- count.incrementAndGet();
- }
- }
AtomicInteger 源碼分析
調(diào)用AtomicInteger類的incrementAndGet方法不用加鎖可以實(shí)現(xiàn)安全的遞增,這個(gè)好神奇,下面帶領(lǐng)大家分析一下源碼是這么實(shí)現(xiàn)的,等不及了等不及了。
打開源碼,可以看到定義的incrementAndGet方法:
- /**
- * 在當(dāng)前值的基礎(chǔ)上自動(dòng)加 1
- *
- * @return 更新后的值
- */
- public final int incrementAndGet() {
- return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
- }
通過源碼可以看到實(shí)際上是調(diào)用了 unsafe 的一個(gè)方法,unsafe 是什么待會(huì)再說。
我們?cè)倏纯磄etAndAddInt方法的參數(shù):第一個(gè)參數(shù) this 是當(dāng)前對(duì)象的引用;第二個(gè)參數(shù)valueOffset是用來記錄value值在內(nèi)存中的偏移地址,第三個(gè)參數(shù)是一個(gè)常量 1;
在 AtomicInteger 中定義了一個(gè)常量valueOffset和一個(gè)可變的成員變量 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 變量保存當(dāng)前對(duì)象的值,valueOffset 是變量的內(nèi)存偏移地址,也是通過調(diào)用unsafe的方法獲取。
- public final class Unsafe {
- // ……省略其他方法
- public native long objectFieldOffset(Field f);
- }
這里再說說 Unsafe 這個(gè)類,人如其名:不安全的類。打開 Unsafe 類會(huì)看到大部分方法都標(biāo)識(shí)了 native,也就是說這些都是本地方法,本地方法強(qiáng)依賴于操作系統(tǒng)平臺(tái),一般都是采用C/C++語言編寫,在調(diào)用 Unsafe 類的本地方法實(shí)際會(huì)執(zhí)行這些方法,熟悉 C/C++的小伙伴可自行下載源碼研究。
好了,我們?cè)倩氐阶铋_始,調(diào)用了 Unsafe 類的getAndAddInt方法:
- public final class Unsafe {
- // ……省略其他方法
- public final int getAndAddInt(Object o, long offset, int delta) {
- int v;
- do {
- v = getIntVolatile(o, offset);
- // 循環(huán) CAS 操作
- } while (!compareAndSwapInt(o, offset, v, v + delta));
- return v;
- }
- // 根據(jù)內(nèi)存偏移地址獲取當(dāng)前值
- public native int getIntVolatile(Object o, long offset);
- // CAS 操作
- public final native boolean compareAndSwapInt(Object o, long offset,
- int expected,
- int x);
- }
通過getIntVolatile方法獲取當(dāng)前 AtomicInteger 對(duì)象的value值,這是一個(gè)本地方法。
然后調(diào)用compareAndSwapInt進(jìn)行 CAS 原子操作,嘗試在當(dāng)前值的基礎(chǔ)上加 1,如果 CAS 失敗會(huì)循環(huán)進(jìn)行重試。
因此compareAndSwapInt方法是最核心的,詳細(xì)實(shí)現(xiàn)大家可以自行找源碼看。這里我們看看方法的參數(shù),一共有四個(gè)參數(shù):o 是指當(dāng)前對(duì)象;offset 是指當(dāng)前對(duì)象值的內(nèi)存偏移地址;expected是期望值;x是修改后的值;
compareAndSwapInt方法的思路是拿到對(duì)象 o 和 offset 后會(huì)再去取對(duì)象實(shí)際的值,如果當(dāng)前值與之前取的期望值是一致的就認(rèn)為 value 沒有被修改過,直接將 value 的值更新為 x,這樣就完成了一次 CAS 操作,CAS 操作是通過操作系統(tǒng)保證原子性的。
如果當(dāng)前值與期望值不一致,說明 value 值被修改過,那么就會(huì)重試 CAS 操作直到成功。
AtomicInteger類中還有很多其他的方法,如:
- decrementAndGet()
- getAndDecrement()
- getAndIncrement()
- accumulateAndGet()
- // …… 省略
這些方法實(shí)現(xiàn)原理都是大同小異,希望大家可以舉一反三理解其他的方法。
另外還有一些其他的類,如:AtomicLong,AtomicReference,AtomicIntegerArray等,這里也不再贅述,原理都是大同小異。
AtomicLong 和 LongAdder 誰更牛?
Java 在 jdk1.8版本 引入了 LongAdder 類,與 AtomicLong 一樣可以實(shí)現(xiàn)加、減、遞增、遞減等線程安全操作,但是在高并發(fā)競(jìng)爭(zhēng)非常激烈的場(chǎng)景下 LongAdder 的效率更勝一籌,后續(xù)單獨(dú)用一篇文章進(jìn)行介紹。
總結(jié)
講了半天,可能有的小伙伴還是比較懵,Atomic 類到底是如何實(shí)現(xiàn)線程安全的?
在語言層面上,Atomic 類是沒有做任何同步操作的,翻看源代碼方法沒有任何加鎖,其實(shí)最大功勞還是在 CAS 身上。CAS 利用操作系統(tǒng)的硬件特性實(shí)現(xiàn)了原子性,利用 CPU 多核能力實(shí)現(xiàn)了硬件層面的阻塞。
只有 CAS 的原子性保證就一定是線程安全的嗎?當(dāng)然不是的,通過源碼發(fā)現(xiàn) value 變量還用了 volatile 修飾了,保證了線程可見性。
那有些小伙伴可能要問了,那是不是加鎖就沒有用了,非也,雖然基于 CAS 的線程安全機(jī)制很好很高效,但是這適合一些粒度比較小的需求才有效,如果遇到非常復(fù)雜的業(yè)務(wù)邏輯還是需要加鎖操作的。
大家學(xué)會(huì)了嗎?
文章題目:面試官:說說Atomic原子類的實(shí)現(xiàn)原理
轉(zhuǎn)載來于:http://m.5511xx.com/article/dhdpejg.html


咨詢
建站咨詢
