Java并發(fā)編程包中atomic的實(shí)現(xiàn)原理示例詳解
線程安全:
當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些進(jìn)程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)調(diào),這個(gè)類都能表現(xiàn)出正確的行為,那么就稱這個(gè)類時(shí)線程安全的。
線程安全主要體現(xiàn)在以下三個(gè)方面:
原子性:提供了互斥訪問,同一時(shí)刻只能有一個(gè)線程對(duì)它進(jìn)行操作
可見性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)的被其他線程觀察到
有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序的存在,該觀察結(jié)果一般雜亂無序
引子
在多線程的場(chǎng)景中,我們需要保證數(shù)據(jù)安全,就會(huì)考慮同步的方案,通常會(huì)使用synchronized或者lock來處理,使用了synchronized意味著內(nèi)核態(tài)的一次切換。這是一個(gè)很重的操作。
有沒有一種方式,可以比較便利的實(shí)現(xiàn)一些簡(jiǎn)單的數(shù)據(jù)同步,比如計(jì)數(shù)器等等。concurrent包下的atomic提供我們這么一種輕量級(jí)的數(shù)據(jù)同步的選擇。
使用例子
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class App {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(100);
AtomicInteger atomicInteger = new AtomicInteger(0);
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
atomicInteger.getAndIncrement();
countDownLatch.countDown();
}
}.start();
}
countDownLatch.await();
System.out.println(atomicInteger.get());
}
}
在以上代碼中,使用AtomicInteger聲明了一個(gè)全局變量,并且在多線程中進(jìn)行自增,代碼中并沒有進(jìn)行顯示的加鎖。
以上代碼的輸出結(jié)果,永遠(yuǎn)都是100。如果將AtomicInteger換成Integer,打印結(jié)果基本都是小于100。
也就說明AtomicInteger聲明的變量,在多線程場(chǎng)景中的自增操作是可以保證線程安全的。接下來我們分析下其原理。
原理
我們可以看一下AtomicInteger的代碼

他的值是存在一個(gè)volatile的int里面。volatile只能保證這個(gè)變量的可見性。不能保證他的原子性。
可以看看getAndIncrement這個(gè)類似i++的函數(shù),可以發(fā)現(xiàn),是調(diào)用了UnSafe中的getAndAddInt。

UnSafe是何方神圣?UnSafe提供了java可以直接操作底層的能力。
進(jìn)一步,我們可以發(fā)現(xiàn)實(shí)現(xiàn)方式:

如何保證原子性:自旋 + CAS(樂觀鎖)。在這個(gè)過程中,通過compareAndSwapInt比較更新value值,如果更新失敗,重新獲取舊值,然后更新。
優(yōu)缺點(diǎn)
CAS相對(duì)于其他鎖,不會(huì)進(jìn)行內(nèi)核態(tài)操作,有著一些性能的提升。但同時(shí)引入自旋,當(dāng)鎖競(jìng)爭(zhēng)較大的時(shí)候,自旋次數(shù)會(huì)增多。cpu資源會(huì)消耗很高。
換句話說,CAS+自旋適合使用在低并發(fā)有同步數(shù)據(jù)的應(yīng)用場(chǎng)景。
Java 8做出的改進(jìn)和努力
在Java 8中引入了4個(gè)新的計(jì)數(shù)器類型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。他們都是繼承于Striped64。
在LongAdder 與AtomicLong有什么區(qū)別?
Atomic*遇到的問題是,只能運(yùn)用于低并發(fā)場(chǎng)景。因此LongAddr在這基礎(chǔ)上引入了分段鎖的概念。可以參考《JDK8系列之LongAdder解析》一起看看做了什么。
大概就是當(dāng)競(jìng)爭(zhēng)不激烈的時(shí)候,所有線程都是通過CAS對(duì)同一個(gè)變量(Base)進(jìn)行修改,當(dāng)競(jìng)爭(zhēng)激烈的時(shí)候,會(huì)將根據(jù)當(dāng)前線程哈希到對(duì)于Cell上進(jìn)行修改(多段鎖)。

可以看到大概實(shí)現(xiàn)原理是:通過CAS樂觀鎖保證原子性,通過自旋保證當(dāng)次修改的最終修改成功,通過降低鎖粒度(多段鎖)增加并發(fā)性能。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java實(shí)現(xiàn)圖書管理系統(tǒng)的示例代碼
這篇文章將通過Java實(shí)現(xiàn)一個(gè)簡(jiǎn)答的圖書管理系統(tǒng),本圖書管理系統(tǒng)用對(duì)象數(shù)組的方式來提供操作方法,比較特別,建議新手學(xué)習(xí),這對(duì)理解Java面向?qū)ο笥泻艽髱椭?/div> 2022-11-11
Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果的任務(wù)
Callable與Future兩功能是Java?5版本中加入的,這篇文章主要給大家介紹了關(guān)于Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-12-12
idea中如何創(chuàng)建scala項(xiàng)目
idea中創(chuàng)建scala項(xiàng)目有三種方式1.通過maven;2.通過idea;3.通過sbt的方式;本文就每種方法通過圖文并茂的形式給大家詳細(xì)介紹,需要的朋友參考下吧2021-07-07
javaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了javaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
Java設(shè)計(jì)模式以虹貓藍(lán)兔的故事講解裝飾器模式
裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對(duì)客戶端透明的方式拓展對(duì)象的功能,是繼承關(guān)系的一種替代方案,本篇文章以虹貓藍(lán)兔生動(dòng)形象的為你帶來詳細(xì)講解2022-04-04最新評(píng)論

