Java線(xiàn)程安全中的原子性淺析
何為原子性
原子性:一條線(xiàn)程在執(zhí)行一系列程序指令操作時(shí),該線(xiàn)程不可中斷。一旦出現(xiàn)中斷,那么就可能會(huì)導(dǎo)致程序執(zhí)行前后的結(jié)果不一致。與數(shù)據(jù)庫(kù)中的原子性(事務(wù)管理體現(xiàn))是相同的
概括:一段程序只能由一條線(xiàn)程去完整的執(zhí)行,不能被多個(gè)線(xiàn)程干擾執(zhí)行
以最經(jīng)典的轉(zhuǎn)賬為例,甲向乙的賬戶(hù)轉(zhuǎn)賬500這個(gè)轉(zhuǎn)賬行為就包含了兩個(gè)操作:分別是1. 甲的賬戶(hù)-500 2. 乙的賬戶(hù) +500但如果此時(shí)不能保證原子性操作就可能會(huì)出現(xiàn)甲的賬戶(hù)減了500但是乙的賬戶(hù)沒(méi)有加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)看線(xiàn)程安全的原子性示例:
測(cè)試代碼:
public class Demo{
public static void main(String[] args) {
Temp task = new Temp();
// 啟動(dòng)100條線(xiàn)程
for (int i = 1; i <= 100 ; i++) {
new Thread(task).start();
}
}
}
class Temp implements Runnable{
private int count = 0;
@Override
public void run() {
// 線(xiàn)程任務(wù):將count
for (int i = 1; i <= 100; i++) {
count++;
System.out.println(Thread.currentThread().getName()+"::count====>>"+count);
}
}
}

上述代碼實(shí)現(xiàn)將Count從0加到10000,每一條線(xiàn)程控制count加100,100條線(xiàn)程啟動(dòng)執(zhí)行實(shí)現(xiàn)。但是在執(zhí)行過(guò)程中會(huì)發(fā)現(xiàn)會(huì)出現(xiàn)幾次無(wú)法達(dá)到10000的結(jié)果,產(chǎn)生的原因就是假設(shè)當(dāng)某一條線(xiàn)程在執(zhí)行count累加時(shí)執(zhí)行到了count=97時(shí)(也就是該線(xiàn)程執(zhí)行失敗,并且提交了執(zhí)行失敗的結(jié)果)cpu被其他線(xiàn)程拿到,那么其他線(xiàn)程繼續(xù)拿到count=97進(jìn)行累加,這樣就導(dǎo)致最后結(jié)果不準(zhǔn)確這個(gè)時(shí)候線(xiàn)程就不是安全的,也就是說(shuō)我們這段程序是不具備原子性的。
但是有時(shí)效果不是很明顯,建議可以將線(xiàn)程數(shù)增加到500條;
那么原子性的問(wèn)題如何解決呢?
解決方法
加鎖–> 悲觀(guān)鎖(阻塞同步)
利用synchronized修飾的同步方法、同步代碼塊啥的,隨便你怎么上鎖都可以,本質(zhì)上的解決機(jī)理就是保證線(xiàn)程任務(wù)同時(shí)只能被一條線(xiàn)程執(zhí)行,在執(zhí)行完畢之前其他線(xiàn)程無(wú)法拿到執(zhí)行權(quán)。由此來(lái)保證線(xiàn)程的原子性
從時(shí)間維度上來(lái)講,某一時(shí)刻只允許一條線(xiàn)程執(zhí)行線(xiàn)程任務(wù),其他線(xiàn)程就處于阻塞狀態(tài),這種利用阻塞其他線(xiàn)程的方式就稱(chēng)為阻塞同步也叫做互斥同步。大大降低了執(zhí)行效率和性能
這種解決方式采用了悲觀(guān)的并發(fā)策略,synchronized也被稱(chēng)為悲觀(guān)鎖,為什么說(shuō)是悲觀(guān)?因?yàn)槌绦蛟诩渔i之初就默認(rèn)每一次線(xiàn)程操作共享資源時(shí)都會(huì)被其他線(xiàn)程干擾。即在不進(jìn)行同步干預(yù)的情況下,程序默認(rèn)每次線(xiàn)程操作共享資源都會(huì)存在其他線(xiàn)程的競(jìng)爭(zhēng)繼而導(dǎo)致程序執(zhí)行出現(xiàn)問(wèn)題。阻塞同步也就是做出了最壞的打算,故稱(chēng)為悲觀(guān)鎖
使用原子類(lèi)( Atomic )–> 樂(lè)觀(guān)鎖(非阻塞同步)
為什么還要使用原子類(lèi)?
because 利用synchronized加鎖去解決原子性問(wèn)題,性能太低了。如果我們的程序線(xiàn)程數(shù)量太大,那每次線(xiàn)程任務(wù)只能被單條線(xiàn)程執(zhí)行,效率太低了
原子類(lèi)是性能高效、線(xiàn)程安全。并且Java提供了比較全面的原子類(lèi)供開(kāi)發(fā)者使用,下面以AtomicInteger為例來(lái)說(shuō)它的方法使用,多數(shù)原子類(lèi)的方法都有些類(lèi)似
// 導(dǎo)包 import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger整型原子類(lèi)
// 常用方法 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)
除了整型原子類(lèi)之外還有如長(zhǎng)整型原子類(lèi)AtomicLong、布爾原子類(lèi)AtomicBoolean、整型數(shù)組AtomicIntegerArray、長(zhǎng)整型數(shù)組AtomicLongArray、引用數(shù)據(jù)類(lèi)型數(shù)組AtomicReferenceArray等
CAS機(jī)制(Compare And Swap)
什么是CAS?
CAS( Compare And Swap )意為比較并交換,CAS的實(shí)現(xiàn)機(jī)制就是利用了非阻塞同步。
采用樂(lè)觀(guān)的并發(fā)策略,當(dāng)程序運(yùn)行中,我們不再利用阻塞其他線(xiàn)程來(lái)保證當(dāng)前線(xiàn)程正確執(zhí)行的方式,而是作出最優(yōu)情況的解決方案,故而也被稱(chēng)為樂(lè)觀(guān)鎖。
即每一次線(xiàn)程操作共享資源都會(huì)執(zhí)行成功并提交正確的結(jié)果,但是在將最新的結(jié)果提交時(shí),需要與當(dāng)前存儲(chǔ)的值進(jìn)行比較就是進(jìn)行一個(gè)新值與原值沖突檢測(cè),比較完之后如果發(fā)現(xiàn)最新結(jié)果與當(dāng)前值一致則說(shuō)明執(zhí)行失敗,反之則是執(zhí)行成功然后替換到原值即可
我們依然用上述操作count作為示例,如下圖所示

可以看出,不論任何一條線(xiàn)程正在操作count,都可以與其他線(xiàn)程進(jìn)行競(jìng)爭(zhēng)。非阻塞同步大大的節(jié)省了線(xiàn)程阻塞和喚醒的性能開(kāi)銷(xiāo)
下面談一下CAS具體的實(shí)現(xiàn)機(jī)制(CAS算法)
CAS實(shí)現(xiàn)主要用到三個(gè)操作數(shù),分別是 內(nèi)存地址S、原值(預(yù)期值)A、新值B
當(dāng)線(xiàn)程向主內(nèi)存中提交一個(gè)共享變量的新值B時(shí),首先會(huì)將原值A(chǔ)與S處存儲(chǔ)的值進(jìn)行比較,只有當(dāng)兩個(gè)值相同時(shí)(沒(méi)有其他線(xiàn)程干擾),才能將新值B提交至主內(nèi)存更新共享變量
CAS算法真正關(guān)注的是線(xiàn)程提交時(shí)的S處值與預(yù)期值是否相同,但仍然存在A(yíng)BA漏洞,暫時(shí)不做深究
到此這篇關(guān)于Java線(xiàn)程安全中的原子性淺析的文章就介紹到這了,更多相關(guān)Java線(xiàn)程原子性?xún)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis-plus 處理大數(shù)據(jù)插入太慢的解決
這篇文章主要介紹了mybatis-plus 處理大數(shù)據(jù)插入太慢的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
基于SpringBoot項(xiàng)目遇到的坑--Date入?yún)?wèn)題
這篇文章主要介紹了SpringBoot項(xiàng)目遇到的坑--Date入?yún)?wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
在SpringBoot中利用RocketMQ實(shí)現(xiàn)批量消息消費(fèi)功能
RocketMQ 是一款分布式消息隊(duì)列,支持高吞吐、低延遲的消息傳遞,對(duì)于需要一次處理多條消息的場(chǎng)景,RocketMQ 提供了批量消費(fèi)的機(jī)制,這篇文章將展示如何在 Spring Boot 中實(shí)現(xiàn)這一功能,感興趣的小伙伴跟著小編一起來(lái)看看吧2024-11-11
springAop實(shí)現(xiàn)講解(看這篇夠了)
AOP面向切面編程是一種編程范式,它通過(guò)將通用的橫切關(guān)注點(diǎn)(如日志、事務(wù)、權(quán)限控制等)與業(yè)務(wù)邏輯分離,使得代碼更加清晰、簡(jiǎn)潔、易于維護(hù),這篇文章主要介紹了springAop實(shí)現(xiàn)講解(看這篇夠了),需要的朋友可以參考下2024-02-02
java SpringBoot自定義注解,及自定義解析器實(shí)現(xiàn)對(duì)象自動(dòng)注入操作
這篇文章主要介紹了java SpringBoot自定義注解,及自定義解析器實(shí)現(xiàn)對(duì)象自動(dòng)注入操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Java中synchronized實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Java中synchronized實(shí)現(xiàn)原理詳解,涉及synchronized實(shí)現(xiàn)同步的基礎(chǔ),Java對(duì)象頭,Monitor,Mark Word,鎖優(yōu)化,自旋鎖等相關(guān)內(nèi)容,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11
Java concurrency之AtomicReference原子類(lèi)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
AtomicReference是作用是對(duì)"對(duì)象"進(jìn)行原子操作。這篇文章主要介紹了Java concurrency之AtomicReference原子類(lèi),需要的朋友可以參考下2017-06-06
Java 如何快速實(shí)現(xiàn)一個(gè)連接池
有沒(méi)有一個(gè)通用的庫(kù)可以快速實(shí)現(xiàn)一個(gè)線(xiàn)程池呢?得益于 Java 完善的生態(tài),前人們針對(duì)這種需要開(kāi)發(fā)了一個(gè)通用庫(kù):Apache Commons Pool(下文簡(jiǎn)稱(chēng) ACP)。本質(zhì)上來(lái)說(shuō),ACP 庫(kù)提供的是管理對(duì)象池的通用能力,當(dāng)然也可以用來(lái)管理連接池了!2021-05-05
springsecurity基于token的認(rèn)證方式
本文主要介紹了springsecurity基于token的認(rèn)證方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

