摘要:異步非阻塞方式,任務的完成的通知由其他線程發出。并發并行死鎖饑餓活鎖死鎖線程持有,線程持有。如等,在多線程情況下,該操作不是原子級別的而是原子的,所以一般用于狀態標記。
同步/異步、阻塞/非阻塞
同步/異步是 API 被調用者的通知方式。阻塞/非阻塞則是 API 調用者的等待方式(線程掛機/不掛起)。
同步非阻塞
Future方式,任務的完成要主線程自己判斷。
如NIO,后臺有多個任務在執行(非阻塞),主動循環查詢(同步)多個任務的完成狀態,只要有任何一個任務完成,就去處理它。這就是所謂的 “I/O 多路復用”。
同步非阻塞相比同步阻塞:
優點:能夠在等待任務完成的時間里干其他活了(就是 “后臺” 可以有多個任務在同時執行)。
缺點:任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次,而任務可能在兩次輪詢之間的任意時間完成。
異步非阻塞
CompletableFuture方式,任務的完成的通知由其他線程發出。
如AIO,應用程序發起調用,而不需要進行輪詢,進而處理下一個任務,只需在I/O完成后通過信號或是回調將數據傳遞給應用程序即可。
異步非阻塞相比同步非阻塞:
不需要主動輪詢,減少CPU操作。
死鎖
線程A持有lock1,線程B持有lock2。當A試圖獲取lock2時,此時線程B也在試圖獲取lock1。此時二者都在等待對方所持有鎖的釋放,而二者卻又都沒釋放自己所持有的鎖,這時二者便會一直阻塞下去。
饑餓
對于非公平隊列來說,線程有可能一直獲取不到對鎖的占用。
活鎖
由于某些條件沒有滿足,導致兩個線程一直互相“謙讓”對鎖的占用,從而一直等下去。活鎖有可能自行解開,死鎖則不能。
什么是線程安全如果多個線程同時運行你的代碼,每一個線程每次運行結果和單線程運行的結果是一樣的,就是線程安全的。
原子性、可見性、有序性原子性
即一個操作或者多個操作 要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。
可見性
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
有序性
即程序執行的順序按照代碼的先后順序執行。(在單線程中,編譯器對代碼的重排序沒有問題,但在多線程程序運行就可能有問題)
x =?10;?????????//語句1
y = x;?????????//語句2
x++;???????????//語句3
x = x +?1;?????//語句4
上面4個語句只有語句1的操作具備原子性。也就是說,只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量)才是原子操作。
Java內存模型只保證了基本讀取和賦值是原子性操作,如果要實現更大范圍操作的原子性,可以通過synchronized和Lock來實現。
編譯器可能會對程序操作做重排序(為了讓CPU指令處理的流水線更加高效,減少空閑時間)。編譯器在重排序時,會遵守數據依賴性,不會改變存在數據依賴關系的兩個操作的執行順序。
注意,這里所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的數據依賴性不被編譯器和處理器考慮。
所以重排序會使得多線程不安全。
volatile修飾的變量不保留拷貝,直接訪問主內存中的變量,即保證可見性。
volatile前面的代碼肯定在volatile之前,volatile后面的代碼肯定在volatile之后,即保證有序性。
volatile修飾的變量缺少原子性的保證。如volatile n=n+1、n++、n = m + 1 等,在多線程情況下,該操作不是原子級別的;而n=false是原子的,所以volatile一般用于狀態標記。如果自己沒有把握,可以使用synchronized、Lock、AtomicInteger來代替volatile。
synchronized與static synchronized 的區別:
synchronized是對類的當前方法的實例進行加鎖,類的兩個不同實例的synchronized方法可以被兩個線程分別訪問。
static synchronized是類java.lang.Class對象鎖。因為當虛擬機加載一個類的時候,會會為這個類實例化一個 java.lang.Class 對象。類的不同實例在執行該方法時共用一個鎖。
synchronized方法只能由synchronized的方法覆蓋:
繼承時子類的覆蓋方法必須定義成synchronized。
兩個線程不能同時訪問同一對象的不同synchronized方法:
因為synchronized鎖是基于對象的。但同一對象的普通方法和synchronized方法能同時被兩個線程分別訪問。
?程序順序原則:一個線程內保證語義的串行性
?volatile規則:volatile變量的寫,先發生于讀,這保證了volatile變量的可見性
?鎖規則:解鎖(unlock)必然發生在隨后的加鎖(lock)前
?傳遞性:A先于B,B先于C,那么A必然先于C
?線程的start()方法先于它的每一個動作
?線程的所有操作先于線程的終結(Thread.join())
?線程的中斷(interrupt())先于被中斷線程的代碼
?對象的構造函數執行結束先于finalize()方法
這些原則保證了重排的語義是一致的。
CAS算法:CAS(V, E, N)。V表示要更新的變量,E表示預期值,N表示新值。僅當V值等于E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什么都不做。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,并成功更新,其余均會失敗。失敗的線程不會被掛起,僅是被告知失敗,并且允許再次嘗試,當然也允許失敗的線程放棄操作。
以AtomicInteger為例:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; private volatile int value; //保證線程間的數據是可見的 static { try { //valueOffset就是value valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } public final boolean compareAndSet(int expect, int update) { //對于this這個類上的偏移量為valueOffset的變量值如果與期望值expect相同,那么把這個變量的值設為update。 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
以上compareAndSet方法類似以下:
if (value ==?expect) { Value =?update; return true; } else { return false; }
那么問題來了,如果value==?expect之后,正要執行value=?update時,切換了線程更改了值,則會造成了數據不一致。但這個擔心是多余的,因為CAS操作是原子的,中間不會有線程切換。
如何保證原子性,即一個步驟?
實際上compareAndSet()利用JNI(Java Native Interface)來執行CPU的CMPXCHG指令,從而保證比較、交換是一步操作,即原子性操作。
ABA問題
static final AtomicReferenceref = new AtomicReference (1); public final int incrementAndGet() { while (true) { int current = ref.get(); int next = current + 1; // 1 if (ref.compareAndSet(current, next)) { // 2 return next; } } }
在代碼1和代碼2之間,若其他線程將value設置為3,另一個線程又將value設置1,則CAS進行檢查時會錯誤的認為值沒有發生變化,但是實際上卻變化了。這就是A變成B又變成A,即ABA問題。
解決思路就是添加版本號。在變量和版本號綁定,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A,當版本號相同時才做更新值的操作。
java.util.concurrent.atomic.AtomicStampedReference
private static class Pair{ final T reference; final int stamp; ...... }
AtomicStampedReference的compareAndSet方法會首先檢查當前reference是否==預期reference(內存地址比較),并且當前stamp是否等于預期stamp,如果都相等,則執行Unsafe.compareAndSwapObject方法。
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Paircurrent = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); //Unsafe.compareAndSwapObject }
循環時間長開銷大
自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的PAUSE指令那么效率會有一定的提升,PAUSE指令提升了自旋等待循環(spin-wait loop)的性能。
只能保證一個共享變量的原子操作
對于多個共享變量操作,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者把多個共享變量合并成一個共享變量來操作。JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。
普通變量的原子操作
java.util.concurrent.atomic.AtomicIntegerFieldUpdater
就比如原本有一個變量是int型,并且很多地方都應用了這個變量,但是在某個場景下,想讓int型變成AtomicInteger,但是如果直接改類型,就要改其他地方的應用。AtomicIntegerFieldUpdater就是為了解決這樣的問題產生的。
public static class V { volatile int score; public int getScore() { return score; } public void setScore(int score) { this.score = score; } } public final static AtomicIntegerFieldUpdatervv = AtomicIntegerFieldUpdater.newUpdater(V.class, "score"); public static void main(String[] args) { final V stu = new V(); vv.incrementAndGet(stu); }
注:
Updater只能修改它可見范圍內的變量。因為Updater使用反射得到這個變量。
變量必須是volatile類型的。
由于CAS操作會通過對象實例中的偏移量(堆內存的偏移量)直接進行賦值,因此,它不支持static字段(Unsafe.objectFieldOffset()不支持靜態變量)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67261.html
摘要:筆記來源并發編程與高并發解決方案并發基礎綜述多級緩存緩存一致性亂序執行優化內存模型規定抽象結構同步八種操作及規則并發的優勢與風險并發與高并發基本概念基本概念并發同時擁有兩個或者多個線程,如果程序在單核處理器上運行,多個線程將交替地換入或者換 筆記來源:【IMOOC】Java并發編程與高并發解決方案 并發基礎 綜述: CPU多級緩存:緩存一致性、亂序執行優化 Java內存模型:JM...
摘要:這里呢,我直接給出高并發場景通常都會考慮的一些解決思路和手段結尾如何有效的準備面試中并發類問題,我已經給出我的理解。 showImg(https://segmentfault.com/img/bV7Viy?w=550&h=405); 主題 又到面試季了,從群里,看到許多同學分享了自己的面試題目,我也抽空在網上搜索了一些許多公司使用的面試題,目前校招和社招的面試題基本都集中在幾個大方向上...
摘要:前言三年后端開發經驗,面的目標崗位是的高級后端開發。面試結束,應該沒有后續。 前言 三年Java后端開發經驗,面的目標崗位是20k-35k的高級后端Java開發。 第一場,基本裸面,關于曾經的項目部門答的不好,所以還是得好好準備。 某C輪在線旅游公司 筆試 先做半個小時的筆試題,一共六個題目,兩道go語言的基礎題,一道斐波那契相關,一道數據庫行列轉置,一道實現一個棧,還有一道是百萬計...
摘要:淺談秒殺系統架構設計后端掘金秒殺是電子商務網站常見的一種營銷手段。這兩個項目白話網站架構演進后端掘金這是白話系列的文章。 淺談秒殺系統架構設計 - 后端 - 掘金秒殺是電子商務網站常見的一種營銷手段。 不要整個系統宕機。 即使系統故障,也不要將錯誤數據展示出來。 盡量保持公平公正。 實現效果 秒殺開始前,搶購按鈕為活動未開始。 秒殺開始時,搶購按鈕可以點擊下單。 秒殺結束后,按鈕按鈕變...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
閱讀 3702·2021-11-11 11:00
閱讀 2180·2021-10-08 10:05
閱讀 2671·2021-10-08 10:04
閱讀 3204·2021-09-30 09:48
閱讀 3763·2021-09-27 14:10
閱讀 1704·2021-09-09 09:33
閱讀 2100·2019-08-30 15:55
閱讀 1602·2019-08-30 13:53