日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java非阻塞锁_Java并发问题的非阻塞解决方案

發(fā)布時間:2023/12/2 java 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java非阻塞锁_Java并发问题的非阻塞解决方案 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)自http://blog.csdn.net/u011277203/article/details/9223545

在并發(fā)環(huán)境中,對于共享資源通常會采用顯式的鎖機制(比如synchronized或ReentrantLock)來保證在任意時刻只會有一條線程訪問這些變量,并且這些變量的修改對隨后獲取鎖的線程是可見的。無法獲取鎖的線程會進入阻塞狀態(tài),并被JVM和操作系統(tǒng)掛起,在未來某一時刻被調(diào)度重新獲取鎖,掛起和恢復線程會產(chǎn)生很多的系統(tǒng)消耗和較長時間的中斷。

線程的切換同時會引起上下文切換,即把當前線程的運行時上下文保存起來,裝入新線程的運行時上下文,所以上下文切換并不是免費的,另外被換入的新線程所需要的數(shù)據(jù)不太可能在CPU Cache中,因此上下文切換會導致Cache Missing激增,新線程開始執(zhí)行時,性能會相對較低。

在高并發(fā),競爭非常激烈的場景下,顯式鎖的開銷會非常大,將嚴重影響系統(tǒng)的性能。所以在一些場景下,使用非阻塞方案解決并發(fā)問題會顯著提升系統(tǒng)性能。

Volatile

Volatile變量是一種比鎖更輕量的同步機制,因為它不會產(chǎn)生上下文切換和線程調(diào)度。Volatile變量可以保證可見性,即變量被一條線程修改后,其他線程都會讀取到最新值,不過volatile變量也有自身的限制:它不支持原子操作,當變量的更新依賴其他變量或自身時(比如i++),volatile不能保證結(jié)果的正確性。

最近在項目中需要封裝Memcache客戶端,實現(xiàn)雙機熱備和自動切換。這里設置一個變量currentCluster來表示當前使用的是主集群還是備用集群,業(yè)務請求根據(jù)此變量來判斷優(yōu)先訪問的集群,同時有1條守護線程不斷輪詢2個集群,一旦發(fā)現(xiàn)有機器不可用,立刻切換,即修改變量currentCluster的值。所以currentCluster變量會被多條線程讀寫,且訪問非常頻繁,不過currentCluster變量的修改并沒有依賴其他變量或自身,只需要保證可見性即可,適用與volatile變量的應用場景:

由于主存的訪問速度相對于CPU的處理速度比較慢,所以CPU通過Cache降低內(nèi)存延時的成本,編譯器和CPU本身會對指令做一些優(yōu)化,改變指令的執(zhí)行順序,在不改變最終結(jié)果的前提下,提高CPU Cache的命中率和CPU流水線的執(zhí)行效率,此過程稱為指令重排。當數(shù)據(jù)不可變或者限制在同一條線程范圍內(nèi),CPU Cache和指令重排是無害的,但是如果在多核處理器,并發(fā)訪問共享可變狀態(tài)的場景下,不同的Cache緩存的數(shù)據(jù)可能會不一致,共享可變狀態(tài)的內(nèi)存操作被重新排序,這些優(yōu)化會造成程序行為不定,造成共享變量的不可見性。在Java程序中加入volatile關鍵字可有效解決這些問題。

在C語言中對volatile關鍵字修飾的共享變量執(zhí)行寫操作的引發(fā)2件事情:

1. ?將當前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存

2. ?這個寫回內(nèi)存的操作會引起在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效

JVM增強了Java中volatile關鍵字的語義,在訪問變量時,會加入內(nèi)存屏障,使得前后指令不會被重排。因此Java中的volatile關鍵字可以保證可見性,即共享變量被修改后,其他線程立刻可以讀取到最新值。

由于volatile變量在被修改時會將CPUCache中的數(shù)據(jù)失效掉,而CPU Cache的最小執(zhí)行單位是Cache Line,所以包含volatile變量的整條Cache Line的數(shù)據(jù)都會失效。這里需要注意”偽共享”問題,如果volatile變量長度不超過Cache Line,在相鄰變量之間需要padding,否則會產(chǎn)生大量Cache Missing。這里對于CPU的細節(jié)并不展開討論,感興趣的同學可以閱讀振輝在4月Rigel技術(shù)月刊中發(fā)表的文章《優(yōu)化到 CPU ––java 與 CPU 緩存》

原子操作

上一小節(jié)中提到volatile關鍵字只能保證可見性,如果變量的修改依賴其他變量或自身,則volatile無能為力,此時需要實現(xiàn)原子性,即所有操作是不可分割的,不會被其他線程打斷。在現(xiàn)代多核處理器中,都會提供原子指令,比如CAS(compare and swap),該指令有3個操作數(shù):需要操作的內(nèi)存地址V,預期的原有值oldValue,要寫入的新值newValue。使用CAS指令執(zhí)行更新操作時,如果V上的值和oldValue相同,則原子的V上的值更新為newValue,如果在當前線程讀取oldValue之后,其他線程執(zhí)行了更新操作,則當前線程的CAS指令返回失敗。

當多條線程同時試圖使用CAS指令更新同一個共享變量時,會有一條線程成功更新變量,而其他線程會失敗。由于這里應沒有涉及鎖的操作,所以失敗線程并不會被掛起,也不會阻塞,他們只是被告知這次更新操作失敗,可以重試或做其他的事情。下面的一段示例代碼是一個計數(shù)器的非阻塞實現(xiàn),在increment的過程中,不斷使用CAS操作更新變量,直到成功為止,java.util.concurrent包中的原子實現(xiàn)也是采用類似的機制。

利用CAS指令除了能夠?qū)崿F(xiàn)簡單數(shù)據(jù)類型的原子操作(比如java.util.concurrent包中的AtomicInteger,AtomicLong)外,還能實現(xiàn)復雜數(shù)據(jù)類型的原子操作。實現(xiàn)復雜數(shù)據(jù)類型的非阻塞算法的關鍵在于如何在維護數(shù)據(jù)一致性時,將原子更新的范圍限定在一個簡單變量上。比如一個棧,每個元素Node(value, next)只會指向一個其他的元素,并且每個元素也只會被一個其他的元素指向。對于push方法,會創(chuàng)建一個新節(jié)點指向棧頂元素top,并使用java.util.concurrent.atomic.AtomicReference 的CAS操作嘗試替換top元素,如果top沒有被其他線程修改,則替換成功,否則重新獲取top元素,再次嘗試替換,知道成功為止。

自旋鎖

當程序中需要保證多個資源或變量的一致性,無法將更新范圍限定在一個變量上時,必須使用顯式的鎖機制,比如synchronized關鍵字或ReentrantLock,但如上文所述,由于會產(chǎn)生阻塞,這種顯式鎖機制的開銷比較大,尤其是在高并發(fā)場景下。這里介紹一種非阻塞的顯式鎖機制——自旋鎖。

自旋鎖采用java.util.concurret包提供的AtomicBoolean類表示鎖的狀態(tài),false表示沒有其他線程獲取當前鎖,true表示當前鎖已被其他線程獲取。當有多條線程同時訪問lock()方法,試圖獲取鎖時,只有一條線程可以成功,其他線程會停留在while(state.get()){}循環(huán)中,只有當活動線程調(diào)用unlock()方法釋放鎖時,才會有另一條線程跳出while(state.get()){}循環(huán),因為unlock方法將state設置為false。由于這里沒有獲取到鎖的線程并沒有被阻塞,所以不會有阻塞相關的開銷。

一條線程成功獲取鎖后,所有的非活動線程都會不停的循環(huán),競爭會非常激烈,造成CPU資源的浪費。所以可以引入讓步鎖的機制降低CPU的開銷。

當線程獲取鎖失敗時,會調(diào)用backoff()方法時sleep一段時間,避免多條線程同時循環(huán),并且每條線程恢復的時間不一樣,減少了競爭,降低了CPU的開銷。這里讓步鎖的sleep時長設定非常關鍵,如果太短,效果不明顯,如果太長,會降低系統(tǒng)的吞吐量。根據(jù)同步塊的預計運行時長來設置比較合理。

總結(jié)

在實際應用場景中,為避免阻塞帶來的開銷,使用非阻塞方案解決并發(fā)問題是非常有必要的。當更新范圍可以限定在一個變量上時,可以使用volatile關鍵字或原子操作。如果需要保證多個資源或變量的一致性,則可以考慮自旋鎖,不過對于同步塊執(zhí)行時間較長或執(zhí)行時間長度差距較大的場景,并不適合使用自旋鎖,因為很難避免CPU的過度開銷,所以這種場景下,不妨直接使用synchronized關鍵字或ReentrantLock。

事實上Synchronized關鍵字和ReentrantLock都在不同程度上實現(xiàn)了自旋鎖,在競爭開始時,會先嘗試自旋,如果能夠獲取鎖,則直接返回,否則進入阻塞狀態(tài)。不過這里的自旋時長并不可控,如果已經(jīng)確定同步塊會執(zhí)行比較快(一般來說沒有IO和復雜計算),直接使用自旋鎖效果會更佳。關于synchronized關鍵字和ReentrantLock的內(nèi)部實現(xiàn)原理,后面會專門寫文章詳細討論。

總結(jié)

以上是生活随笔為你收集整理的java非阻塞锁_Java并发问题的非阻塞解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。