您好,登錄后才能下訂單哦!
本篇內容介紹了“Java線程安全中的原子性是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
原子性:一條線程在執行一系列程序指令操作時,該線程不可中斷。一旦出現中斷,那么就可能會導致程序執行前后的結果不一致。與數據庫中的原子性(事務管理體現)是相同的
概括:一段程序只能由一條線程去完整的執行,不能被多個線程干擾執行
以最經典的轉賬為例,甲向乙的賬戶轉賬500這個轉賬行為就包含了兩個操作:分別是1. 甲的賬戶-500 2. 乙的賬戶 +500但如果此時不能保證原子性操作就可能會出現甲的賬戶減了500但是乙的賬戶沒有加500的情況
首先我們先來區分哪些是原子操作哪些非原子操作:
int a = 1; // (1) int b = a; // (2) a += b; // (3)
(1)一個操作,就是將值“1” 賦給 變量a
(2)兩個操作,首先獲取a的值,然后將a的值賦給b
(3)四個操作,首先獲取b的值,再獲取a的值,再將a與b的值相加,再將相加后的值賦給a
所以只有(1)和(3)屬于原子操作,(2)不構成原子操作
上述舉例,我們清楚了原子操作就是一個不可分割的操作。
下面我們來看線程安全的原子性示例:
測試代碼:
public class Demo{ public static void main(String[] args) { Temp task = new Temp(); // 啟動100條線程 for (int i = 1; i <= 100 ; i++) { new Thread(task).start(); } } } class Temp implements Runnable{ private int count = 0; @Override public void run() { // 線程任務:將count for (int i = 1; i <= 100; i++) { count++; System.out.println(Thread.currentThread().getName()+"::count====>>"+count); } } }
上述代碼實現將Count從0加到10000,每一條線程控制count加100,100條線程啟動執行實現。但是在執行過程中會發現會出現幾次無法達到10000的結果,產生的原因就是假設當某一條線程在執行count累加時執行到了count=97時(也就是該線程執行失敗,并且提交了執行失敗的結果)cpu被其他線程拿到,那么其他線程繼續拿到count=97進行累加,這樣就導致最后結果不準確這個時候線程就不是安全的,也就是說我們這段程序是不具備原子性的。
但是有時效果不是很明顯,建議可以將線程數增加到500條;
那么原子性的問題如何解決呢?
加鎖–> 悲觀鎖(阻塞同步)
利用synchronized修飾的同步方法、同步代碼塊啥的,隨便你怎么上鎖都可以,本質上的解決機理就是保證線程任務同時只能被一條線程執行,在執行完畢之前其他線程無法拿到執行權。由此來保證線程的原子性
從時間維度上來講,某一時刻只允許一條線程執行線程任務,其他線程就處于阻塞狀態,這種利用阻塞其他線程的方式就稱為阻塞同步也叫做互斥同步。大大降低了執行效率和性能
這種解決方式采用了悲觀的并發策略,synchronized也被稱為悲觀鎖,為什么說是悲觀?因為程序在加鎖之初就默認每一次線程操作共享資源時都會被其他線程干擾。即在不進行同步干預的情況下,程序默認每次線程操作共享資源都會存在其他線程的競爭繼而導致程序執行出現問題。阻塞同步也就是做出了最壞的打算,故稱為悲觀鎖
使用原子類( Atomic )–> 樂觀鎖(非阻塞同步)
為什么還要使用原子類?
because 利用synchronized加鎖去解決原子性問題,性能太低了。如果我們的程序線程數量太大,那每次線程任務只能被單條線程執行,效率太低了
原子類是性能高效、線程安全。并且Java提供了比較全面的原子類供開發者使用,下面以AtomicInteger為例來說它的方法使用,多數原子類的方法都有些類似
// 導包 import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger整型原子類
// 常用方法 public final int set(int newValue) //為當前對象賦值 public final int get() //獲取當前值 public final int getAndSet(int newValue)//獲取當前值然后重新賦值 public final int getAndIncrement()//先獲取當前值然后自增 public final int getAndDecrement() //先獲取當前值然后自減 public final int getAndAdd(int delta) //獲取當前值然后加上delta boolean compareAndSet(int expect, int update) //如果當前值等于預期值,則以原子方式將該值設置為輸入值(update)
除了整型原子類之外還有如長整型原子類AtomicLong、布爾原子類AtomicBoolean、整型數組AtomicIntegerArray、長整型數組AtomicLongArray、引用數據類型數組AtomicReferenceArray等
什么是CAS?
CAS( Compare And Swap )意為比較并交換,CAS的實現機制就是利用了非阻塞同步。
采用樂觀的并發策略,當程序運行中,我們不再利用阻塞其他線程來保證當前線程正確執行的方式,而是作出最優情況的解決方案,故而也被稱為樂觀鎖。
即每一次線程操作共享資源都會執行成功并提交正確的結果,但是在將最新的結果提交時,需要與當前存儲的值進行比較就是進行一個新值與原值沖突檢測,比較完之后如果發現最新結果與當前值一致則說明執行失敗,反之則是執行成功然后替換到原值即可
我們依然用上述操作count作為示例,如下圖所示
可以看出,不論任何一條線程正在操作count,都可以與其他線程進行競爭。非阻塞同步大大的節省了線程阻塞和喚醒的性能開銷
下面談一下CAS具體的實現機制(CAS算法)
CAS實現主要用到三個操作數,分別是 內存地址S、原值(預期值)A、新值B
當線程向主內存中提交一個共享變量的新值B時,首先會將原值A與S處存儲的值進行比較,只有當兩個值相同時(沒有其他線程干擾),才能將新值B提交至主內存更新共享變量
CAS算法真正關注的是線程提交時的S處值與預期值是否相同,但仍然存在ABA漏洞,暫時不做深究。
“Java線程安全中的原子性是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。