溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java線程安全中的原子性是什么

發(fā)布時(shí)間:2023-02-22 17:41:08 來(lái)源:億速云 閱讀:132 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Java線程安全中的原子性是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

何為原子性

原子性:一條線程在執(zhí)行一系列程序指令操作時(shí),該線程不可中斷。一旦出現(xiàn)中斷,那么就可能會(huì)導(dǎo)致程序執(zhí)行前后的結(jié)果不一致。與數(shù)據(jù)庫(kù)中的原子性(事務(wù)管理體現(xiàn))是相同的

概括:一段程序只能由一條線程去完整的執(zhí)行,不能被多個(gè)線程干擾執(zhí)行

以最經(jīng)典的轉(zhuǎn)賬為例,甲向乙的賬戶轉(zhuǎn)賬500這個(gè)轉(zhuǎn)賬行為就包含了兩個(gè)操作:分別是1. 甲的賬戶-500 2. 乙的賬戶 +500但如果此時(shí)不能保證原子性操作就可能會(huì)出現(xiàn)甲的賬戶減了500但是乙的賬戶沒有加500的情況

首先我們先來(lái)區(qū)分哪些是原子操作哪些非原子操作:

int a = 1; // (1)
int b = a; // (2) 
a += b;    // (3)

(1)一個(gè)操作,就是將值“1” 賦給 變量a

(2)兩個(gè)操作,首先獲取a的值,然后將a的值賦給b

(3)四個(gè)操作,首先獲取b的值,再獲取a的值,再將a與b的值相加,再將相加后的值賦給a

所以只有(1)和(3)屬于原子操作,(2)不構(gòu)成原子操作

上述舉例,我們清楚了原子操作就是一個(gè)不可分割的操作。

下面我們來(lái)看線程安全的原子性示例:

測(cè)試代碼:

public class Demo{
    public static void main(String[] args) {
        Temp task = new Temp();
        // 啟動(dòng)100條線程
        for (int i = 1; i <= 100 ; i++) {
            new Thread(task).start();
        }
    }
}
class Temp implements Runnable{
    private int count = 0;
    @Override
    public void run() {
        // 線程任務(wù):將count
        for (int i = 1; i <= 100; i++) {
            count++;
            System.out.println(Thread.currentThread().getName()+"::count====>>"+count);
        }
    }
}

Java線程安全中的原子性是什么

上述代碼實(shí)現(xiàn)將Count從0加到10000,每一條線程控制count加100,100條線程啟動(dòng)執(zhí)行實(shí)現(xiàn)。但是在執(zhí)行過程中會(huì)發(fā)現(xiàn)會(huì)出現(xiàn)幾次無(wú)法達(dá)到10000的結(jié)果,產(chǎn)生的原因就是假設(shè)當(dāng)某一條線程在執(zhí)行count累加時(shí)執(zhí)行到了count=97時(shí)(也就是該線程執(zhí)行失敗,并且提交了執(zhí)行失敗的結(jié)果)cpu被其他線程拿到,那么其他線程繼續(xù)拿到count=97進(jìn)行累加,這樣就導(dǎo)致最后結(jié)果不準(zhǔn)確這個(gè)時(shí)候線程就不是安全的,也就是說我們這段程序是不具備原子性的。

但是有時(shí)效果不是很明顯,建議可以將線程數(shù)增加到500條;

那么原子性的問題如何解決呢?

解決方法

加鎖&ndash;> 悲觀鎖(阻塞同步)

利用synchronized修飾的同步方法、同步代碼塊啥的,隨便你怎么上鎖都可以,本質(zhì)上的解決機(jī)理就是保證線程任務(wù)同時(shí)只能被一條線程執(zhí)行,在執(zhí)行完畢之前其他線程無(wú)法拿到執(zhí)行權(quán)。由此來(lái)保證線程的原子性

從時(shí)間維度上來(lái)講,某一時(shí)刻只允許一條線程執(zhí)行線程任務(wù),其他線程就處于阻塞狀態(tài),這種利用阻塞其他線程的方式就稱為阻塞同步也叫做互斥同步。大大降低了執(zhí)行效率和性能

這種解決方式采用了悲觀的并發(fā)策略,synchronized也被稱為悲觀鎖,為什么說是悲觀?因?yàn)槌绦蛟诩渔i之初就默認(rèn)每一次線程操作共享資源時(shí)都會(huì)被其他線程干擾。即在不進(jìn)行同步干預(yù)的情況下,程序默認(rèn)每次線程操作共享資源都會(huì)存在其他線程的競(jìng)爭(zhēng)繼而導(dǎo)致程序執(zhí)行出現(xiàn)問題。阻塞同步也就是做出了最壞的打算,故稱為悲觀鎖

使用原子類( Atomic )&ndash;> 樂觀鎖(非阻塞同步)

為什么還要使用原子類?

because 利用synchronized加鎖去解決原子性問題,性能太低了。如果我們的程序線程數(shù)量太大,那每次線程任務(wù)只能被單條線程執(zhí)行,效率太低了

原子類是性能高效、線程安全。并且Java提供了比較全面的原子類供開發(fā)者使用,下面以AtomicInteger為例來(lái)說它的方法使用,多數(shù)原子類的方法都有些類似

// 導(dǎo)包
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger整型原子類

// 常用方法
public final int set(int newValue) //為當(dāng)前對(duì)象賦值
public final int get() //獲取當(dāng)前值
public final int getAndSet(int newValue)//獲取當(dāng)前值然后重新賦值
public final int getAndIncrement()//先獲取當(dāng)前值然后自增
public final int getAndDecrement() //先獲取當(dāng)前值然后自減
public final int getAndAdd(int delta) //獲取當(dāng)前值然后加上delta
boolean compareAndSet(int expect, int update) //如果當(dāng)前值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入值(update)

除了整型原子類之外還有如長(zhǎng)整型原子類AtomicLong、布爾原子類AtomicBoolean、整型數(shù)組AtomicIntegerArray、長(zhǎng)整型數(shù)組AtomicLongArray、引用數(shù)據(jù)類型數(shù)組AtomicReferenceArray等

CAS機(jī)制(Compare And Swap)

什么是CAS?

CAS( Compare And Swap )意為比較并交換,CAS的實(shí)現(xiàn)機(jī)制就是利用了非阻塞同步。

采用樂觀的并發(fā)策略,當(dāng)程序運(yùn)行中,我們不再利用阻塞其他線程來(lái)保證當(dāng)前線程正確執(zhí)行的方式,而是作出最優(yōu)情況的解決方案,故而也被稱為樂觀鎖。

即每一次線程操作共享資源都會(huì)執(zhí)行成功并提交正確的結(jié)果,但是在將最新的結(jié)果提交時(shí),需要與當(dāng)前存儲(chǔ)的值進(jìn)行比較就是進(jìn)行一個(gè)新值與原值沖突檢測(cè),比較完之后如果發(fā)現(xiàn)最新結(jié)果與當(dāng)前值一致則說明執(zhí)行失敗,反之則是執(zhí)行成功然后替換到原值即可

我們依然用上述操作count作為示例,如下圖所示

Java線程安全中的原子性是什么

可以看出,不論任何一條線程正在操作count,都可以與其他線程進(jìn)行競(jìng)爭(zhēng)。非阻塞同步大大的節(jié)省了線程阻塞和喚醒的性能開銷

下面談一下CAS具體的實(shí)現(xiàn)機(jī)制(CAS算法)

CAS實(shí)現(xiàn)主要用到三個(gè)操作數(shù),分別是 內(nèi)存地址S、原值(預(yù)期值)A、新值B

當(dāng)線程向主內(nèi)存中提交一個(gè)共享變量的新值B時(shí),首先會(huì)將原值A(chǔ)與S處存儲(chǔ)的值進(jìn)行比較,只有當(dāng)兩個(gè)值相同時(shí)(沒有其他線程干擾),才能將新值B提交至主內(nèi)存更新共享變量

CAS算法真正關(guān)注的是線程提交時(shí)的S處值與預(yù)期值是否相同,但仍然存在ABA漏洞,暫時(shí)不做深究。

“Java線程安全中的原子性是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI