久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術(shù)文章
文章詳情頁

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

瀏覽:71日期:2022-08-10 18:12:27
目錄一、從硬件原語上理解同步(非特指Java)1.1、基本硬件原語1.2、用一致性實(shí)現(xiàn)鎖1.3、使用上面的旋轉(zhuǎn)鎖實(shí)現(xiàn)我們一個同步原語——柵欄同步二、Java中的原子性操作概述三、Java中的CAS操作概述3.1、模擬ABA問題3.2、使用AtomicStampedReference重新實(shí)現(xiàn)四、Java中的Unsafe類4.1、Unsafe類中的重要方法介紹4.2、Unsafe類的使用五、JUC中原子操作類AtomicLong的原理探究5.1、原操作類概述5.2、AtomicLong的源碼一、從硬件原語上理解同步(非特指Java)

同步機(jī)制是多處理機(jī)系統(tǒng)的重要組成部分,其實(shí)現(xiàn)方式除了關(guān)系到計算的正確性之外還有效率的問題。同步機(jī)制的實(shí)現(xiàn)通常是在硬件提供的同步指令的基礎(chǔ)上,在通過用戶級別軟件例程實(shí)現(xiàn)的。上面說到的樂觀策略實(shí)際上就是建立在硬件指令集的基礎(chǔ)上的(我們需要實(shí)際操作和沖突檢測是原子性的),一般有下面的常用指令:測試并設(shè)置(test_and_set)、獲取并增加(fetch_and_increment)、原子交換(Atomic_Exchange)、比較并交換(CAS)、加載連接條件存儲(LL/SC),下面我們會講到這些以及通過這些硬件同步原語實(shí)現(xiàn)的旋轉(zhuǎn)鎖和柵欄同步。

1.1、基本硬件原語

在多處理機(jī)中實(shí)現(xiàn)同步,所需的主要功能是一組能以原子操作讀出并修改存儲單元的硬件原語。如果沒有這種操作,建立基本的同步原語的代價會非常大。基本硬件原語有幾種形式提供選擇,他們都能以原子操作的方式讀改存儲單元,并指出進(jìn)行的操作是否能以原子形式進(jìn)行,這些原語作為基本構(gòu)建提供構(gòu)造各種各樣的用戶及同步操作。

一個典型的例子就是原子交換(Atomic Exchange),他的功能是將一個存儲單元中的值和一個寄存器的值進(jìn)行交換。我們看看這個原語怎樣構(gòu)造一個我們通常意義上說的簡單的鎖。

假設(shè)現(xiàn)在我們構(gòu)造這樣一個簡單的鎖:其值為0表示鎖是開的(鎖可用),為1表示上鎖(不可用)。當(dāng)處理器要給該鎖上鎖的時候,將對應(yīng)于該鎖的存儲單元的值與存放在某個寄存器中的1進(jìn)行交換。如果別的處理器已經(jīng)上了鎖,那么交換指令返回的值為1否則為0。返回0的時候,因?yàn)槭窃咏粨Q,鎖的值就會從0變?yōu)?表示上鎖成功;返回1,原子交換鎖的值還是1,但是返回1表示已經(jīng)被上了鎖。我們考慮使用這個鎖:假設(shè)兩個處理器同時進(jìn)行交換操作(原子交換),競爭的結(jié)果就是,只有一個處理器會先執(zhí)行成功而得到返回值0,而另一個得到的返回值為1表示已經(jīng)被上鎖。從這些我們可以看出,采用原子交換指令是實(shí)現(xiàn)同步的關(guān)鍵:這個原子交換操作的不可再分的,兩個交換操作將由寫順序機(jī)制確定先后順序,這也保證了兩個線程不能同時獲取同步變量鎖。

除此之外,還有別的原語可以實(shí)現(xiàn)同步(關(guān)鍵都在于能以原子的方式讀-改-寫存儲單元的值)。例如:測試并置定(test_and_set)(先測試一個存儲單元的值,如果符合條件就修改其值),另一個同步原語是讀取并加1(fetch_and_increment))(返回存儲單元的值并自動增加該值)。

那么,上面的基本原語操作又是怎樣實(shí)現(xiàn)的呢,這在一條指令中完成上述操作顯然是困難的(在一條不可中斷的指令中完成一次存儲器的讀改寫,而且要求不允許其他的訪存操作還要避免死鎖)。現(xiàn)在的計算機(jī)上采用一對指令來實(shí)現(xiàn)上述的同步原語。該指令對由兩條特殊的指令組成,一條是特殊的load指令(LL指令),另一條是特殊的store指令(SC)。指令的執(zhí)行順序是:如果LL指令指明的存儲單元的值在SC對其進(jìn)行寫之前被其他的指令改寫過,則第二條指令執(zhí)行失敗,如果在兩條指令之間進(jìn)行切換也會導(dǎo)致執(zhí)行SC失敗,而SC指令將通過返回一個值來指出該指令操作是否成功(如果返回的1表示執(zhí)行成功,返回0表示失敗)。為什么說這對指令相當(dāng)于原子操作呢,這指的是是所有其他處理器進(jìn)行的操作或者在這對指令之前執(zhí)行或者在其后執(zhí)行,不存在兩條指令之間進(jìn)行,所以在這一對指令之間不存在任何其他處理器改變相應(yīng)存儲單元的值。

下面是一段實(shí)現(xiàn)對R1指出的存儲單元進(jìn)行的原子交換操作

try:OR    R3,R4,R0 //R4中為交換值,將該值送入R3

    LL    R2,0(R1) //將0(R1)中的值取到R2

    SC    R3,0(R1) //若0(R1)中的值與R3中的值相同,則置R3的值為1,否則為0

    BEQZ R3,try //R3的值為0表示存失敗,轉(zhuǎn)移重新嘗試

    MOV R4,R2 //成功,將取出的值送往R4     

最終R4和由R1指向的存儲單元值進(jìn)行了原子交換,在LL和SC之間如果有別的處理器插入并且修改了存儲單元的值則SC都會返回0并存入R3中從而重新執(zhí)行交換操作。下面是實(shí)現(xiàn)各個講到的讀取并加1(fetch_and_increment)原語的實(shí)現(xiàn)

try:LL    R2,0(R1) //將0(R1)中的值送入R2

    DADDIU    R2,R2,#1 //加1操作(R2+1->R2)

    SC    R2,0(R1) //如果0(R1)中的值和R2中的值相同就置R2的值為1,否則為0

    BEQZ    R2,try //R2的值為0表示存失敗,轉(zhuǎn)移到開始出重新執(zhí)行

上面的指令的執(zhí)行需要跟蹤地址,通常LL指令指定一個寄存器,該寄存器中存放著目的存儲單元的地址,這個寄存器稱為連接寄存器,如果發(fā)生中斷切換或者與連接寄存器中的地址匹配的cache塊被作廢(被別的SC指令訪問),則將連接寄存器清零,SC指令則檢查它的存儲地址和連接寄存器匯中的內(nèi)容是夠匹配,如果匹配則SC指令繼續(xù)執(zhí)行,否則執(zhí)行失敗。

1.2、用一致性實(shí)現(xiàn)鎖

我們現(xiàn)在用上面的原子交換的同步原語實(shí)現(xiàn)自旋鎖(spin lock)(處理器不停請求獲得鎖的試用權(quán),圍繞該鎖反復(fù)執(zhí)行循環(huán)程序,直到獲得鎖)。自旋鎖適用于這樣的場景:鎖被占用時間少,在獲得鎖之后加鎖的過程延遲小。

下面我們考慮使用一種簡單的方法實(shí)現(xiàn):將鎖變量保存在存儲器中,處理器可以不斷通過原子交換操作來請求其使用權(quán),比如使用原子交換操作獲得其返回值從而直達(dá)鎖變量的使用情況。釋放鎖的時候,處理器只需要將說置為0。如下面的程序:使用原子交換操作堆自旋鎖進(jìn)行加鎖,其中R1中存放的是自旋鎖變量的地址

        DADDIU R2,R0,#1

lockit: EXCH R2,0(R1) //原子交換,獲得自旋鎖的值并在下面比較自旋鎖的值為1還是0,為1表示已經(jīng)上鎖

        BNEZ R2,lockit //若R2的內(nèi)容不為0,則表示已經(jīng)有其他程序獲得了鎖變量,就繼續(xù)旋轉(zhuǎn)等待

下面我們對這個簡單的自旋鎖實(shí)現(xiàn)進(jìn)行一些改進(jìn)(下面說到的可類比JMM內(nèi)存模型理解)如果計算機(jī)支持Cache一致性,就可以將鎖調(diào)入Cache中(類比本地內(nèi)存),并通過一致性保證使得鎖的值保持和存儲器中的值一致(類比內(nèi)存可見性和本地內(nèi)存主內(nèi)存的值一致同步)。這樣做有下面的好處:①使得環(huán)繞自旋鎖的線程(自旋請求鎖變量)只對本地Cache中的鎖(主存中的副本)進(jìn)行操作,而不用再每次請求占用鎖時候進(jìn)行一次全局的訪存操作(訪問主內(nèi)存存儲器中存放的鎖的值) ②利用訪問鎖的程序局部性原理(處理器最近使用的鎖可能不久后還會使用),這種情況就可以使得鎖駐留在對應(yīng)的Cache中,大大減少了獲得鎖所需要的時間(處于性能考慮,需要減少全局訪存操作)。

在改進(jìn)之前,我們應(yīng)該知道,在上面的簡單實(shí)現(xiàn)的基礎(chǔ)上(上面的每次循環(huán)交換均需要一次寫操作,因?yàn)橛卸鄠€處理器會同時請求加鎖,這就會導(dǎo)致一個處理器請求成功后,其他處理器都會寫不命中),需要對這個程序進(jìn)行改進(jìn),使得它只對本地副本中的鎖變量進(jìn)行讀取和檢測,直到發(fā)現(xiàn)鎖已經(jīng)被釋放。發(fā)現(xiàn)釋放之后,立刻去進(jìn)行交換操作跟別的處理器競爭鎖變量。所有這些進(jìn)程還是以原子交換的方式獲得鎖,也只有一個進(jìn)程可以獲得成功(獲得鎖變量成功的進(jìn)程交換后看到的鎖變量值為0,交換之后的鎖變量值為1表示上鎖成功;而獲得失敗的進(jìn)程雖然也交換了鎖變量的值,但是因?yàn)榻粨Q后自己看到的鎖變量的值已經(jīng)是1,就表示自己進(jìn)程失敗了),其他的需要繼續(xù)旋轉(zhuǎn)等待。當(dāng)獲得鎖的進(jìn)程使用完之后,將鎖變量置為0表示釋放鎖由其他需要獲取的進(jìn)程去競爭它(其他進(jìn)程會在自己的Cache中發(fā)現(xiàn)鎖變量的值發(fā)生變化,這是上面所說的Cache一致性)。下面是修改后的旋轉(zhuǎn)鎖程序

lockit: LD    R2,0(R1) //取得鎖的值

        BNEZ R2,lockit //如果鎖還沒有釋放(R2的值還是1)

        DADDIU    R2,R0,#1 //將R2值置為1(這里面可以這樣想:上面BNEZ執(zhí)行失敗表示R2值為0,那么這個時候就+1)

        EXCH R2,0(R1) //將R2中的值和0(R1)中的鎖變量進(jìn)行原子交換

        BNEZ R2,lockit //上面第一次判斷是當(dāng)前進(jìn)程首先發(fā)現(xiàn)主存中的鎖變量值發(fā)生變化;

                       //進(jìn)行原子交換結(jié)果判斷和上面一樣,如果狡猾后返回值為0表示成功,為1表示失敗就繼續(xù)旋轉(zhuǎn)等待獲取

1.3、使用上面的旋轉(zhuǎn)鎖實(shí)現(xiàn)我們一個同步原語——柵欄同步

首先解釋一下什么叫柵欄同步(barrier)。假設(shè)有一個類似于柵欄的東西,它會強(qiáng)制所有到達(dá)柵欄的進(jìn)程進(jìn)行等待,直到全部的進(jìn)程都到達(dá)之后釋放所有到達(dá)的進(jìn)程繼續(xù)往下執(zhí)行,從而形成同步。下面我們就通過上面說的旋轉(zhuǎn)鎖來簡單模擬實(shí)現(xiàn)這樣的一個同步原語

使用兩個旋轉(zhuǎn)鎖,一個表示計數(shù)器,記錄已經(jīng)到達(dá)該柵欄的進(jìn)程數(shù);另一個用來封鎖進(jìn)程知道最后一個進(jìn)程到達(dá)該柵欄。為了實(shí)現(xiàn)柵欄,我們需要一個變量,到達(dá)并阻塞住的進(jìn)程需要在這個變量上自旋等待知道滿足它需要的條件(都到達(dá)柵欄然后才能往下執(zhí)行)。我們使用spin表示這個條件condition。如下的程序所示,其中l(wèi)ock和unlock提供基本的旋轉(zhuǎn)鎖,變量count記錄已經(jīng)到達(dá)柵欄的進(jìn)程數(shù),total表示已經(jīng)到達(dá)柵欄的進(jìn)程總數(shù),對counterlock加鎖保證了增量操作的原子性,release用來封鎖最后一個到達(dá)柵欄的進(jìn)程。spin(release==1)表示需要全部進(jìn)程都到達(dá)柵欄。

lock(counterlock); //確保更新的原子性if(count == 0) release = 0; //第一個進(jìn)程到達(dá),這時候重置release為0表示在其值變?yōu)?之前后續(xù)到達(dá)的進(jìn)程都需要等待count = count + 1; //記錄到達(dá)的進(jìn)程數(shù)unlock(counterlock); //釋放鎖if(count == total) { //進(jìn)程全部到達(dá) count = 0; //重置計數(shù)器count release = 1; //將release置為1表示釋放所欲到達(dá)的進(jìn)程} else { //進(jìn)程還沒有全部到達(dá) spin(release == 1); //已經(jīng)到達(dá)的進(jìn)程旋轉(zhuǎn)等待知道所有的進(jìn)程到達(dá)(言外之意就是release=1)}

但是上面的這種簡單實(shí)現(xiàn)還是存在問題的,我們考慮下面這種可能發(fā)生的情況:當(dāng)柵欄的使用在循環(huán)當(dāng)中時候,這時候所有釋放的進(jìn)程在運(yùn)行一段時間之后還會到達(dá)柵欄,假設(shè)其中一個進(jìn)程在上次釋放的時候還沒有來得及離開柵欄,而是依舊停留在旋轉(zhuǎn)操作上(可能操作系統(tǒng)重新進(jìn)行進(jìn)程調(diào)度導(dǎo)致那個進(jìn)程沒有來得及離開柵欄)。如果第二次柵欄使用的時候,一個執(zhí)行較快的進(jìn)程到達(dá)柵欄(這個快的意思是,當(dāng)他到達(dá)柵欄之后上次那個還沒有離開柵欄的進(jìn)程還在旋轉(zhuǎn)操作上),這個快的進(jìn)程會發(fā)現(xiàn)count=0,那么他就會將release置為0,這時候就會導(dǎo)致那個還在旋轉(zhuǎn)等待的進(jìn)程發(fā)現(xiàn)release值為0,然后那就更不會再退出這個旋轉(zhuǎn)操作了,就相當(dāng)于被捆綁在柵欄上出不去(這個問題會導(dǎo)致后續(xù)的count計數(shù)少了一個進(jìn)程到達(dá),而總是小于total),那這樣的話,由于count總是小于total那不是所有到達(dá)柵欄的進(jìn)程都在spin上一直自旋了嗎。那怎么解決這個問題呢,一種方法就是在進(jìn)程離開柵欄的時候也進(jìn)行計數(shù),在上次使用柵欄的進(jìn)程全部離開柵欄之前不允許執(zhí)行快的進(jìn)程再次使用并初始化柵欄的一些變量值。還有一種方法是使用sense_reversing柵欄,即每個進(jìn)程只用一個本地私有變量local_sense并初始化為1,用它和release判斷進(jìn)程是否需要自旋等待。

二、Java中的原子性操作概述

所謂原子操作,就是指執(zhí)行一系列操作的時候,要么全部執(zhí)行要么全部不執(zhí)行,不存在只執(zhí)行一部分的情況。在設(shè)置計數(shù)器的時候一般是讀取當(dāng)前的值,然后+1在更新(讀-改-寫的過程),如果不能保證這這幾個操作的過程的原子性就可能出現(xiàn)線程安全問題,比如下面的代碼示例,++value在沒有任何額外保證的前提下不是原子操作。

public class ThreadUnSafe{ private Long value; public Long getValue() {return value;} public void increment() {++value;}}

使用Javap -c XX.class查看匯編代碼如下

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

這是個復(fù)合操作,是不具備原子性的。而保證這個操作原子性的方法最簡單的就是加上synchronized關(guān)鍵字,使用synchronized可以實(shí)現(xiàn)線程安全性,但是這是個獨(dú)占鎖,沒有獲取內(nèi)部鎖的線程會被阻塞住(即便是這里的getValue操作,多線程訪問也會阻塞住),這對于并發(fā)性能的提高是不好的(而這里也不能簡單的去掉getValue上的synchronized,因?yàn)樽x操作需要保證value的讀一致性,即需要獲得主內(nèi)存中的值而不是線程工作內(nèi)存中的可能是舊的副本值)。那么除了加鎖之外其他安全的方法?后面講到的原子類(使用CAS實(shí)現(xiàn))就可以作為一個選擇。

三、Java中的CAS操作概述

Java中提供非阻塞的volatile關(guān)鍵字解決保證共享變量的可見性問題,但是不能解決部分符合操作不具備原子性的問題(比如自增運(yùn)算)。CAS即CompareAndSwap是JDK提供的非阻塞原子操作,通過硬件保證比較更新的原子性。我們通過compareAndSwapLong來簡單介紹CAS:

compareAndSwapLong(Object obj, long valueOffset, long expect, long update),該方法中compareAndSwap表示比較并交換,方法中有四個操作數(shù),其中obj表示對象內(nèi)存的位置,valueOffset表示對象中存儲變量的偏移量,expect表示變量的預(yù)期值,update表示更新值。操作含義就是,若果對象obj中內(nèi)存偏移量為valueOffset的變量值為expect則使用心得update值替換舊的值expect,這是處理器提供的一個原子指令。這些方法有sun.misc.Unsafe類提供。后面我們會說到Unsafe類

在此之前我們先說一下CAS操作的一個經(jīng)典的ABA問題:假如線程1 使用CAS修改初始值為A的變量X,那么線程1會首先回去當(dāng)前變量X的值(A),然后使用CAS操作嘗試修改X的值為B,如果使用CAS修改成功了,那么程序一定執(zhí)行正確了嗎?在往下的假設(shè)看,如果線程I在獲取變量X的值A(chǔ)后,在執(zhí)行CAS之前線程II使用CAS修改變量X的值為B然后由修改回了A。這時候雖然線程I執(zhí)行CAS時候X的值依舊是A但是這個A已經(jīng)不是線程I獲取時候的A了,這就是ABA問題。ABA產(chǎn)生的原因是變量的狀態(tài)值產(chǎn)生了環(huán)形轉(zhuǎn)換,即變量值從A->B,然后又從B->A。jdk中提供了帶有標(biāo)記的原子類AtomicStampedReference(時間戳原子引用)通過控制變量的版本保證CAS的正確性。如下所做的測試ABA問題以及使用AtomicStampedReference來解決這個問題

3.1、模擬ABA問題

下面的程序輸出結(jié)果會是這樣的

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

package test;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicReference;public class TestAtomicStampedReference { static AtomicReference<Integer> atomicReference = new AtomicReference<>(1); public static void main(String[] args) {Thread t1 = new Thread(new Runnable() { @Override public void run() {atomicReference.compareAndSet(1,2);atomicReference.compareAndSet(2,1);System.out.println(Thread.currentThread() + '線程修改后的變量值' + atomicReference.get()); }});Thread t2 = new Thread(new Runnable() { @Override public void run() {//sleep 1秒,保證線程t1完成1->2->1的模擬ABA操作try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}atomicReference.compareAndSet(1,3);System.out.println(Thread.currentThread() + '線程修改后的變量值' + atomicReference.get()); }});t1.start();t2.start(); }}3.2、使用AtomicStampedReference重新實(shí)現(xiàn)

下面是運(yùn)行結(jié)果

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

package test;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicStampedReference;public class TestAtomicStampedReference { static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1); //定義初始值和初始版本號 public static void main(String[] args) {Thread t1 = new Thread(new Runnable() { @Override public void run() {//線程1獲得初始版本號并sleep1秒int version = atomicStampedReference.getStamp();System.out.println(Thread.currentThread() + '當(dāng)前線程獲得的版本號' + version);try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}System.out.println(Thread.currentThread() + '修改變量結(jié)果true/false?:' +atomicStampedReference.compareAndSet(10,11,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1)+ '修改后的結(jié)果:' + atomicStampedReference.getReference());System.out.println(Thread.currentThread() + '修改變量結(jié)果true/false?:' +atomicStampedReference.compareAndSet(11,10,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1)+ '修改后的結(jié)果:' + atomicStampedReference.getReference()); }});Thread t2 = new Thread(new Runnable() { @Override public void run() {//首先獲得初始版本號,sleep2秒讓線程1完成10->11->10的模擬ABA操作int version = atomicStampedReference.getStamp();System.out.println(Thread.currentThread() + '當(dāng)前線程獲得的版本號' + version);try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) { e.printStackTrace();}System.out.println(Thread.currentThread() + '修改變量結(jié)果true/false?:' +atomicStampedReference.compareAndSet(10,20,version,atomicStampedReference.getStamp()+1)+ '修改后的結(jié)果:' + atomicStampedReference.getReference()); }});t1.start();t2.start(); }}四、Java中的Unsafe類

JDK中的rt.jar包中的Unsafe類提供了硬件級別的原子性操作

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

Unsafe類中許多方法都是native方法,他們使用JNI的方式訪問本地C++中的實(shí)現(xiàn)庫。下面我們了解一下Unsafe類提供的幾個主要的方法以及如何使用unsafe類進(jìn)行一些編程操作。

4.1、Unsafe類中的重要方法介紹

(1)public native long objectFieldOffset(Field var1):返回指定的變量在所屬類中的內(nèi)存偏移地址,該偏移地址僅僅在該Unsafe函數(shù)中訪問指定字段時候使用。如下使用Unsafe類獲取變量value在Atomic對象中的內(nèi)存偏移量

 淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

(2)public native int arrayBaseOffset(Class<?> var1):獲取數(shù)組中第一個元素的地址

(3)public native int arrayIndexScale(Class<?> var1):獲取數(shù)組中一個元素占用的字節(jié)

(4)public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5):比較對象var1中的偏移量為var2的變量的值是否與var4相同,相同則使用var6的值更新,并返回true,否則返回false。

(5)public native long getLongVolatile(Object var1, long var2):獲取對象var1中偏移量為offset的變量對應(yīng)volatile語義的值。

(6)public native void putLongVolatile(Object var1, long var2, long var4):設(shè)置var1對象中offset偏移類型為long的值為var4,支持volatile語義

(7)public native void putOrderedLong(Object var1, long var2, long var4):設(shè)置對象obj中offset偏移地址對應(yīng)的long型的field的值為value。這是一個有延遲的putLongVolatile方法,并且不保證對應(yīng)的值類型的修改對其他線程可見,只有變量在只用volatile修飾并且預(yù)計會被意外修改的時候才會使用該方法、

(8)public native void park(boolean var1, long var2):阻塞當(dāng)前線程,其中參數(shù)var1等于false且var2等于0表示一直阻塞,var2大于0表示等待指定的時間后阻塞線程會被喚醒。這個var的值是相對的,為一個增量值,也就是相當(dāng)當(dāng)前時間累加事假后當(dāng)前線程就會被喚醒。如果var1位true,并且var2大于0,則表示阻塞的線程到指定的時間點(diǎn)后就會被喚醒,這里的時間var2是個絕對時間,是某個時間點(diǎn)換算為ms后的值。

(9)public native void unpark(Object var1):喚醒調(diào)用park方法之后的線程。

下面是jdk8之后新增加的,我們列出Long類型的方法

(10)getAndSetLong()方法:獲取當(dāng)前對象var1中偏移量為var2的變量volatile語義的當(dāng)前值,并設(shè)置變量volatile語義的值為var4。

首先使用getLongVolatile獲取當(dāng)前變量的值,然后使用CAS原子操作設(shè)置新的值。這里使用while是當(dāng)CAS失敗時候進(jìn)行重試。

 淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

(11)getAndAddLong()方法:獲取對象var1中偏移量為var2變量的volatile語義的值,設(shè)置變量值為原始值+var4

 淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

4.2、Unsafe類的使用

考慮編寫出下面的程序,并在自己的IDE中運(yùn)行下面的程序,觀察結(jié)果。

package test;import sun.misc.Unsafe;public class TestUnsafe { //獲取Unsafe的實(shí)例 static Unsafe unsafe = Unsafe.getUnsafe(); //記錄變量value在TestUnsafe中的偏移量 static long valueState; //變量 private volatile long value; static {try { //獲取value變量在TestUnsafe類中的偏移量 valueState = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField('value'));} catch (Exception e) { System.out.println(e.getMessage());} } public static void main(String[] args) {TestUnsafe testUnsafe = new TestUnsafe();System.out.println(unsafe.compareAndSwapInt(testUnsafe,valueState,0,1)); }}

上面的程序中首先獲取Unsafe的一個實(shí)例,然后使用unsafe的objectFieldOffset方法獲取TestUnsafe類中value變量,計算在TestUnsafe類中value變量的內(nèi)存偏移地址并保存到valueState中。main中調(diào)用unsafe的compareAndSwapInt方法設(shè)置testUnsafe對象的value變量的值為1(如果是0的話)。value初始默認(rèn)是0,我們希望代碼能輸出true(即compareAndSwapInt能夠執(zhí)行成功),但是最終運(yùn)行時下面的結(jié)果

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

我們看到上面的異常報錯在getUnsafe方法位置,下來我們看一看getUnsafe方法

public static Unsafe getUnsafe() { //(1)獲取調(diào)用getUnsafe類的這個Class類,按照上面的程序中的TestUnsafa類 Class var0 = Reflection.getCallerClass(); //(2)看下面的那個方法 if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException('Unsafe'); } else {return theUnsafe; }}/** * (3)判斷是不是啟動類加載器加載的類,即看看是不是由BootStrapClassLoader加載的TestUnsafe.class, * 由于我們這是一個簡單測試類,是由應(yīng)用程序類加載器AppClassLoader加載的,所以直接報出SecurityException異常 */public static boolean isSystemDomainLoader(ClassLoader var0) { return var0 == null;}

由于Unsafe類rt.jar包提供的,該包下面的類都是通過Bootstrap類加載器加載的,而我們使用的main方法所在的類是由AppClassLoader加載的,所以在main方法中加載Unsafe類的時候根據(jù)雙親委派機(jī)制會委托給Bootstrap加載。那么如果想要使用Unsafe類應(yīng)該怎樣使用呢,《深入理解java虛擬機(jī)》中這一塊告訴我們可以使用反射來使用,下面我們來試一下

package test;import sun.misc.Unsafe;import java.lang.reflect.Field;public class TestUnsafe2 { static Unsafe unsafe; static long valueOffset; private volatile long value = 0; static {try { //使用反射獲取Unsafe的成員變量theUnsafe Field field = Unsafe.class.getDeclaredField('theUnsafe'); //設(shè)置為課存取 field.setAccessible(true); //設(shè)置該變量的值 unsafe = (Unsafe) field.get(null); //獲取value偏移量 valueOffset = unsafe.objectFieldOffset(TestUnsafe2.class.getDeclaredField('value'));} catch (NoSuchFieldException e) { e.printStackTrace();} catch (IllegalAccessException e) { e.printStackTrace();} } public static void main(String[] args) {TestUnsafe2 test = new TestUnsafe2();System.out.println('修改變量結(jié)果true/false?:' +unsafe.compareAndSwapInt(test,valueOffset,0,1)+ '修改后的結(jié)果:' + test.value); }}

得到下面的結(jié)果:

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

五、JUC中原子操作類AtomicLong的原理探究5.1、原操作類概述

JUC包中提供了很多原子操作類,這些類都是通過上面說到的非阻塞CAS算法來實(shí)現(xiàn)的,相比較使用鎖來實(shí)現(xiàn)原子性操作CAS在性能上有很大提高。由于原子操作類的原理都大致相同,所以下面分析AtomicLong類的實(shí)現(xiàn)原理來進(jìn)一步了解原子操作類。

5.2、AtomicLong的源碼

下面是AtomicLong原子類的部分源碼,其中主要包含其成員變量以及一些靜態(tài)代碼塊和構(gòu)造方法

public class AtomicLong extends Number implements java.io.Serializable { //(1)獲取Unsafe實(shí)例 private static final Unsafe unsafe = Unsafe.getUnsafe(); //(2)保存value值的偏移量 private static final long valueOffset; //(3)判斷當(dāng)前JVM是否支持Long類型的無鎖CAS static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); private static native boolean VMSupportsCS8(); static {try { //(4)獲取value值在AtomicLong中的偏移量 valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField('value'));} catch (Exception ex) { throw new Error(ex); } } //(5)實(shí)際存的變量值value private volatile long value; //構(gòu)造方法 public AtomicLong(long initialValue) {value = initialValue; }}

在上面的部分代碼中,代碼(1)通過Unsafe.getUnsafe()方法獲取到Unsafe類的實(shí)例(AtomicLong類也是rt.jar包下面的,所以AtomicLong也是通過啟動類加載器進(jìn)行類加載的)。(2)(4)兩處是計算并保存AtomicLong類中存儲的變量value的偏移量。(5)中的value被聲明為volatile的,這是為了在多線程下保證內(nèi)存的可見性,而value就是具體存放計數(shù)的變量。下面我們看看AtomicLong中的主要幾個函數(shù)

(1)遞增和遞減的源碼

//使用unsafe的方法,原子性的設(shè)置value值為原始值+1,返回值為遞增之后的值public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L);}public final long incrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;}//使用unsafe的方法,原子性的設(shè)置value值為原始值-1,返回值為遞減之后的值public final long getAndDecrement() { return unsafe.getAndAddLong(this, valueOffset, -1L);}public final long decrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;}

在上面的代碼中都是通過調(diào)用Unsafe類的getAndAddLong方法來實(shí)現(xiàn)操作的,我們來看看這個方法,這個方法是個原子性操作:其中的第一個參數(shù)是AtomicLong實(shí)例的引用,第二個參數(shù)是value變量在AtomicLong中的偏移量,第三個參數(shù)是要設(shè)置為第二個變量的值。下面就是getAndAddLong方法的實(shí)現(xiàn),以及一些分析

public final long getAndAddLong(Object var1, long var2, long var4) { long var6; do {//public native long getLongVolatile(Object var1, long var2);//該方法就是獲取var1引用指向的內(nèi)存地址中偏移量為var2位置的值,然后賦給var6var6 = this.getLongVolatile(var1, var2); /**public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); * var1:AtomicXXX類型的一個引用,指向堆內(nèi)存中的一塊地址 * var2:AtomicXXX源碼中的valueOffset,表示AtomicXXX源碼中實(shí)際存儲的值value在原子類型內(nèi)存中的地址偏移量 * var4:要比較的目標(biāo)值expectValue,如果從內(nèi)存指定地址處(var1和var2決定的那塊地址)的值和該值相等,則CAS成功 * var6:CAS成功后向該內(nèi)存中寫進(jìn)的新值 */ //該方法就是使用CAS的方式,比較指定內(nèi)存地址處(var1指向的內(nèi)存地址塊中偏移量為var2處)的值和上面同一塊地址處取出的var6是否相等, //相等就將var6+var4(這里可以看成var6+1)和指定內(nèi)存地址處(var2引用指向的地址塊中偏移量為var2處)的值交換,并返回true,然后就會結(jié)束循環(huán) //CAS失敗返回false,然后繼續(xù)執(zhí)行循環(huán)體內(nèi)部的代碼,直到成功(也就是自增運(yùn)算成功就會跳出循環(huán)并返回自增后的值) } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); return var6;}

(2)CompareAndSet方法

下面是compaerAndSet方法的實(shí)現(xiàn),主要還是調(diào)用unsafe類的compareAndSwapLong方法,其原理和上面分析的差不多,都是通過CAS的方式進(jìn)行比較交換值。

public final boolean compareAndSet(long expect, long update) { //public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}

(3)擴(kuò)展,下面是compareAndSwapInt的底層實(shí)現(xiàn),實(shí)際上是通過硬件同步原語來實(shí)現(xiàn)的CAS,下面的cmpxchg就是基于硬件原語實(shí)現(xiàn)的

UNSAFE_ENTRY(jboolean,Usafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))UsafeWrapper('Usafe_CompareAndSwapInt');oop p = JNIHasdles::resolve(obj);jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);return (jint)(Atomic::cmpxchg(x,addr,e)) == e;UNSAFE_END

(4)下面是一個例子,使用AtomicLong來進(jìn)行技術(shù)運(yùn)算

package test;import java.util.concurrent.atomic.AtomicLong;public class TestAtomic1 { //創(chuàng)建AtomicLong類型的計數(shù)器 private static AtomicLong atomicLong = new AtomicLong();// private static Long atomicLong = 0L; //創(chuàng)建兩個數(shù)組,計算數(shù)組中的0的個數(shù) private static Integer[] arr1 = {0,1,2,3,0,5,6,0,56,0}; private static Integer[] arr2 = {10,1,2,3,0,5,6,0,56,0}; public static void main(String[] args) throws InterruptedException {//線程1統(tǒng)計arr1中0的個數(shù)Thread t1 = new Thread(new Runnable() { @Override public void run() {int size = arr1.length;for (int i = 0; i < size; i++) { if(arr1[i].intValue() == 0) {//atomicLong.getAndIncrement();atomicLong++; }} }});Thread t2 = new Thread(new Runnable() { @Override public void run() {int size = arr2.length;for (int i = 0; i < size; i++) { if(arr2[i].intValue() == 0) {//atomicLong.getAndIncrement();atomicLong++; }} }});t1.start();t2.start();t1.join();t2.join();System.out.println('兩個數(shù)組中0出現(xiàn)的次數(shù)為: ' + atomicLong);//兩個數(shù)組中0出現(xiàn)的次數(shù)為: 7 }}

如果沒有使用原子類型進(jìn)行計數(shù)運(yùn)算,那么可能就是下面的結(jié)果

淺析從同步原語看非阻塞同步以及Java中的應(yīng)用

以上就是淺析從同步原語看非阻塞同步以及Java中的應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于同步 非阻塞同步的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 亚洲综合99 | 三区在线| 亚洲不卡网站 | 在线成人av| 精品国产乱码久久久久久88av | 国产精品视频一二三区 | 国产超碰人人爽人人做人人爱 | 亚洲国产二区三区 | 在线第一页 | 性视频黄色 | 日韩在线视频精品 | 免费日韩精品 | 亚洲超碰av | 午夜精品偷拍 | 青草视频网站 | 天天影视网色香欲综合网无拦截 | 青青久久av北条麻妃海外网 | 91精品国产一区二区三区免费 | 成人欧美一区二区三区白人 | 成人网在线观看 | 欧美一级久久 | 亚洲一区二区在线视频 | 91麻豆产精品久久久 | 成人在线视频播放 | japan护士性xxxⅹhd | 99热国 | 超碰在线99 | 国产在线中文字幕 | 中文字幕2021| 欧美精品1区2区3区 免费亚洲婷婷 | 欧美久久精品 | 日韩一区二区在线观看 | 一区二区三区观看视频 | 在线观看午夜免费视频 | 作爱视频免费看 | 中文字幕精品一区二区三区精品 | 日韩一级二级三级 | 亚洲一区二区三区四区在线观看 | 国产色网站 | 欧美久久一区二区三区 | 日韩一区二区在线电影 |