国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

香港云服务器
服務(wù)器之家 - 編程語言 - JAVA教程 - Java并發(fā)問題之樂觀鎖與悲觀鎖

Java并發(fā)問題之樂觀鎖與悲觀鎖

2021-03-04 09:39拉夫德爾 JAVA教程

這篇文章主要介紹了Java并發(fā)問題之樂觀鎖與悲觀鎖,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

首先介紹一些樂觀鎖悲觀鎖:

悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如java里面的同步原語synchronized關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。

樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂觀鎖。在java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式cas實(shí)現(xiàn)的。

樂觀鎖的一種實(shí)現(xiàn)方式-cas(compare and swap 比較并交換):

鎖存在的問題:

java在jdk1.5之前都是靠 synchronized關(guān)鍵字保證同步的,這種通過使用一致的鎖定協(xié)議來協(xié)調(diào)對(duì)共享狀態(tài)的訪問,可以確保無論哪個(gè)線程持有共享變量的鎖,都采用獨(dú)占的方式來訪問這些變量。這就是一種獨(dú)占鎖,獨(dú)占鎖其實(shí)就是一種悲觀鎖,所以可以說 synchronized 是悲觀鎖。

悲觀鎖機(jī)制存在以下問題:  

1. 在多線程競(jìng)爭(zhēng)下,加鎖、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí),引起性能問題。

2. 一個(gè)線程持有鎖會(huì)導(dǎo)致其它所有需要此鎖的線程掛起。

3. 如果一個(gè)優(yōu)先級(jí)高的線程等待一個(gè)優(yōu)先級(jí)低的線程釋放鎖會(huì)導(dǎo)致優(yōu)先級(jí)倒置,引起性能風(fēng)險(xiǎn)。

對(duì)比于悲觀鎖的這些問題,另一個(gè)更加有效的鎖就是樂觀鎖。其實(shí)樂觀鎖就是:每次不加鎖而是假設(shè)沒有并發(fā)沖突而去完成某項(xiàng)操作,如果因?yàn)椴l(fā)沖突失敗就重試,直到成功為止。

樂觀鎖:

樂觀鎖( optimistic locking )在上文已經(jīng)說過了,其實(shí)就是一種思想。相對(duì)悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)產(chǎn)生并發(fā)沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)是否產(chǎn)生并發(fā)沖突進(jìn)行檢測(cè),如果發(fā)現(xiàn)并發(fā)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。

上面提到的樂觀鎖的概念中其實(shí)已經(jīng)闡述了它的具體實(shí)現(xiàn)細(xì)節(jié):主要就是兩個(gè)步驟:沖突檢測(cè)和數(shù)據(jù)更新。其實(shí)現(xiàn)方式有一種比較典型的就是 compare and swap ( cas )。

cas:

cas是樂觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用cas同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。   

cas 操作中包含三個(gè)操作數(shù) —— 需要讀寫的內(nèi)存位置(v)、進(jìn)行比較的預(yù)期原值(a)和擬寫入的新值(b)。如果內(nèi)存位置v的值與預(yù)期原值a相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值b。否則處理器不做任何操作。無論哪種情況,它都會(huì)在 cas 指令之前返回該位置的值。(在 cas 的一些特殊情況下將僅返回 cas 是否成功,而不提取當(dāng)前值。)cas 有效地說明了“ 我認(rèn)為位置 v 應(yīng)該包含值 a;如果包含該值,則將 b 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。 ”這其實(shí)和樂觀鎖的沖突檢查+數(shù)據(jù)更新的原理是一樣的。

這里再?gòu)?qiáng)調(diào)一下,樂觀鎖是一種思想。cas是這種思想的一種實(shí)現(xiàn)方式。

java對(duì)cas的支持:

在jdk1.5 中新增 java.util.concurrent (j.u.c)就是建立在cas之上的。相對(duì)于對(duì)于 synchronized 這種阻塞算法,cas是非阻塞算法的一種常見實(shí)現(xiàn)。所以j.u.c在性能上有了很大的提升。

以 java.util.concurrent 中的 atomicinteger 為例,看一下在不使用鎖的情況下是如何保證線程安全的。主要理解 getandincrement 方法,該方法的作用相當(dāng)于 ++i 操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class atomicinteger extends number implements java.io.serializable {
  private volatile int value;
 
  public final int get() {
    return value;
  }
 
  public final int getandincrement() {
    for (;;) {
      int current = get();
      int next = current + 1;
      if (compareandset(current, next))
        return current;
    }
  }
 
  public final boolean compareandset(int expect, int update) {
    return unsafe.compareandswapint(this, valueoffset, expect, update);
  }
}

在沒有鎖的機(jī)制下,字段value要借助volatile原語,保證線程間的數(shù)據(jù)是可見性。這樣在獲取變量的值的時(shí)候才能直接讀取。然后來看看 ++i 是怎么做到的。

getandincrement 采用了cas操作,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和 +1 后的結(jié)果進(jìn)行cas操作,如果成功就返回結(jié)果,否則重試直到成功為止。

而 compareandset 利用jni(java native interface)來完成cpu指令的操作:

?
1
2
3
public final boolean compareandset(int expect, int update) { 
  return unsafe.compareandswapint(this, valueoffset, expect, update);
}  

其中unsafe.compareandswapint(this, valueoffset, expect, update);類似如下邏輯:

?
1
2
3
4
5
6
if (this == expect) {
   this = update
   return true;
 } else {
   return false;
 }

那么比較this ==expect,替換this =update,compareandswapint實(shí)現(xiàn)這兩個(gè)步驟的原子性呢? 參考cas的原理

cas原理:

cas通過調(diào)用jni的代碼實(shí)現(xiàn)的。而compareandswapint就是借助c來調(diào)用cpu底層指令實(shí)現(xiàn)的。

下面從分析比較常用的cpu(intel x86)來解釋cas的實(shí)現(xiàn)原理。

下面是sun.misc.unsafe類的compareandswapint()方法的源代碼:

?
1
2
3
public final native boolean compareandswapint(object o, long offset,
                       int expected,
                       int x);

可以看到這是個(gè)本地方法調(diào)用。這個(gè)本地方法在jdk中依次調(diào)用的c++代碼為:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define lock_if_mp(mp) __asm cmp mp, 0 \
            __asm je l0   \
            __asm _emit 0xf0 \
            __asm l0:
 
inline jint   atomic::cmpxchg  (jint   exchange_value, volatile jint*   dest, jint   compare_value) {
 // alternative for interlockedcompareexchange
 int mp = os::is_mp();
 __asm {
  mov edx, dest
  mov ecx, exchange_value
  mov eax, compare_value
  lock_if_mp(mp)
  cmpxchg dword ptr [edx], ecx
 }
}

如上面源代碼所示,程序會(huì)根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴。如果程序是在多處理器上運(yùn)行,就為cmpxchg指令加上lock前綴(lock cmpxchg)。反之,如果程序是在單處理器上運(yùn)行,就省略lock前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性,不需要lock前綴提供的內(nèi)存屏障效果)。

cas缺點(diǎn):

1. aba問題:

比如說一個(gè)線程one從內(nèi)存位置v中取出a,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出a,并且two進(jìn)行了一些操作變成了b,然后two又將v位置的數(shù)據(jù)變成a,這時(shí)候線程one進(jìn)行cas操作發(fā)現(xiàn)內(nèi)存中仍然是a,然后one操作成功。盡管線程one的cas操作成功,但可能存在潛藏的問題。如下所示:

 Java并發(fā)問題之樂觀鎖與悲觀鎖

現(xiàn)有一個(gè)用單向鏈表實(shí)現(xiàn)的堆棧,棧頂為a,這時(shí)線程t1已經(jīng)知道a.next為b,然后希望用cas將棧頂替換為b:

?
1
head.compareandset(a,b);

在t1執(zhí)行上面這條指令之前,線程t2介入,將a、b出棧,再pushd、c、a,此時(shí)堆棧結(jié)構(gòu)如下圖,而對(duì)象b此時(shí)處于游離狀態(tài):

 Java并發(fā)問題之樂觀鎖與悲觀鎖

此時(shí)輪到線程t1執(zhí)行cas操作,檢測(cè)發(fā)現(xiàn)棧頂仍為a,所以cas成功,棧頂變?yōu)閎,但實(shí)際上b.next為null,所以此時(shí)的情況變?yōu)椋?/p>

 Java并發(fā)問題之樂觀鎖與悲觀鎖

其中堆棧中只有b一個(gè)元素,c和d組成的鏈表不再存在于堆棧中,平白無故就把c、d丟掉了。

從java1.5開始jdk的atomic包里提供了一個(gè)類atomicstampedreference來解決aba問題。這個(gè)類的compareandset方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

?
1
2
3
4
5
6
7
8
9
public boolean compareandset(
        v   expectedreference,//預(yù)期引用
 
        v   newreference,//更新后的引用
 
       int  expectedstamp, //預(yù)期標(biāo)志
 
       int  newstamp //更新后的標(biāo)志
)

實(shí)際應(yīng)用代碼:

?
1
2
3
4
5
private static atomicstampedreference<integer> atomicstampedref = new atomicstampedreference<integer>(100, 0);
 
 ........
 
 atomicstampedref.compareandset(100, 101, stamp, stamp + 1);

2.循環(huán)時(shí)間長(zhǎng)開銷大:

自旋cas(不成功,就一直循環(huán)執(zhí)行,直到成功)如果長(zhǎng)時(shí)間不成功,會(huì)給cpu帶來非常大的執(zhí)行開銷。如果jvm能支持處理器提供的pause指令那么效率會(huì)有一定的提升,pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使cpu不會(huì)消耗過多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起cpu流水線被清空(cpu pipeline flush),從而提高cpu的執(zhí)行效率。

3.只能保證一個(gè)共享變量的原子操作:

當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)cas的方式來保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)cas就無法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用cas來操作ij。從java1.5開始jdk提供了atomicreference類來保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來進(jìn)行cas操作。

cas與synchronized的使用情景:   

1、對(duì)于資源競(jìng)爭(zhēng)較少(線程沖突較輕)的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;而cas基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。

2、對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,cas自旋的概率會(huì)比較大,從而浪費(fèi)更多的cpu資源,效率低于synchronized。

補(bǔ)充: synchronized在jdk1.6之后,已經(jīng)改進(jìn)優(yōu)化。synchronized的底層實(shí)現(xiàn)主要依靠lock-free的隊(duì)列,基本思路是自旋后阻塞,競(jìng)爭(zhēng)切換后繼續(xù)競(jìng)爭(zhēng)鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和cas類似的性能;而線程沖突嚴(yán)重的情況下,性能遠(yuǎn)高于cas。

concurrent包的實(shí)現(xiàn):

由于java的cas同時(shí)具有 volatile 讀和volatile寫的內(nèi)存語義,因此java線程之間的通信現(xiàn)在有了下面四種方式:

1. a線程寫volatile變量,隨后b線程讀這個(gè)volatile變量。

2. a線程寫volatile變量,隨后b線程用cas更新這個(gè)volatile變量。

3.a線程用cas更新一個(gè)volatile變量,隨后b線程用cas更新這個(gè)volatile變量。

4.a線程用cas更新一個(gè)volatile變量,隨后b線程讀這個(gè)volatile變量。

java的cas會(huì)使用現(xiàn)代處理器上提供的高效機(jī)器級(jí)別原子指令,這些原子指令以原子方式對(duì)內(nèi)存執(zhí)行讀-改-寫操作,這是在多處理器中實(shí)現(xiàn)同步的關(guān)鍵(從本質(zhì)上來說,能夠支持原子性讀-改-寫指令的計(jì)算機(jī)器,是順序計(jì)算圖靈機(jī)的異步等價(jià)機(jī)器,因此任何現(xiàn)代的多處理器都會(huì)去支持某種能對(duì)內(nèi)存執(zhí)行原子性讀-改-寫操作的原子指令)。同時(shí),volatile變量的讀/寫和cas可以實(shí)現(xiàn)線程之間的通信。把這些特性整合在一起,就形成了整個(gè)concurrent包得以實(shí)現(xiàn)的基石。如果我們仔細(xì)分析concurrent包的源代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)一個(gè)通用化的實(shí)現(xiàn)模式:

1. 首先,聲明共享變量為volatile;  

2.然后,使用cas的原子條件更新來實(shí)現(xiàn)線程之間的同步;

3.同時(shí),配合以volatile的讀/寫和cas所具有的volatile讀和寫的內(nèi)存語義來實(shí)現(xiàn)線程之間的通信。

aqs,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎(chǔ)類都是使用這種模式來實(shí)現(xiàn)的,而concurrent包中的高層類又是依賴于這些基礎(chǔ)類來實(shí)現(xiàn)的。從整體來看,concurrent包的實(shí)現(xiàn)示意圖如下:

      Java并發(fā)問題之樂觀鎖與悲觀鎖

jvm中的cas(堆中對(duì)象的分配): 

java調(diào)用new object()會(huì)創(chuàng)建一個(gè)對(duì)象,這個(gè)對(duì)象會(huì)被分配到j(luò)vm的堆中。那么這個(gè)對(duì)象到底是怎么在堆中保存的呢?

首先,new object()執(zhí)行的時(shí)候,這個(gè)對(duì)象需要多大的空間,其實(shí)是已經(jīng)確定的,因?yàn)閖ava中的各種數(shù)據(jù)類型,占用多大的空間都是固定的(對(duì)其原理不清楚的請(qǐng)自行g(shù)oogle)。那么接下來的工作就是在堆中找出那么一塊空間用于存放這個(gè)對(duì)象。

在單線程的情況下,一般有兩種分配策略:

1.指針碰撞:這種一般適用于內(nèi)存是絕對(duì)規(guī)整的(內(nèi)存是否規(guī)整取決于內(nèi)存回收策略),分配空間的工作只是將指針像空閑內(nèi)存一側(cè)移動(dòng)對(duì)象大小的距離即可。

2.空閑列表:這種適用于內(nèi)存非規(guī)整的情況,這種情況下jvm會(huì)維護(hù)一個(gè)內(nèi)存列表,記錄哪些內(nèi)存區(qū)域是空閑的,大小是多少。給對(duì)象分配空間的時(shí)候去空閑列表里查詢到合適的區(qū)域然后進(jìn)行分配即可。

但是jvm不可能一直在單線程狀態(tài)下運(yùn)行,那樣效率太差了。由于再給一個(gè)對(duì)象分配內(nèi)存的時(shí)候不是原子性的操作,至少需要以下幾步:查找空閑列表、分配內(nèi)存、修改空閑列表等等,這是不安全的。解決并發(fā)時(shí)的安全問題也有兩種策略:

1. cas:實(shí)際上虛擬機(jī)采用cas配合上失敗重試的方式保證更新操作的原子性,原理和上面講的一樣。

2. tlab:如果使用cas其實(shí)對(duì)性能還是會(huì)有影響的,所以jvm又提出了一種更高級(jí)的優(yōu)化策略:每個(gè)線程在java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖區(qū)(tlab),線程內(nèi)部需要分配內(nèi)存時(shí)直接在tlab上分配就行,避免了線程沖突。只有當(dāng)緩沖區(qū)的內(nèi)存用光需要重新分配內(nèi)存的時(shí)候才會(huì)進(jìn)行cas操作分配更大的內(nèi)存空間。

虛擬機(jī)是否使用tlab,可以通過-xx:+/-usetlab參數(shù)來進(jìn)行配置(jdk5及以后的版本默認(rèn)是啟用tlab的)。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://www.cnblogs.com/qjjazry/p/6581568.html

延伸 · 閱讀

精彩推薦
1191
主站蜘蛛池模板: 国产精品69毛片高清亚洲 | 97久久精品 | 一色屋精品久久久久久久久久 | 欧美激情网 | a视频在线 | 亚洲av毛片一区二二区三三区 | 成人在线视频观看 | 日韩视频在线观看 | 亚洲欧美日韩另类一区二区 | 久久精品中文字幕 | 欧美婷婷 | 亚洲伊人久久影院 | 亚洲精品国产乱码在线看蜜月 | 亚洲色图二区 | 色吧欧美 | 午夜视频在线观看网站 | 国产精品成人3p一区二区三区 | 天天爽夜夜爽夜夜爽精品视频 | 日本手机在线视频 | 一级黄色毛片 | 久久人爽 | 国产精品尤物在线观看 | 激情五月婷婷在线 | 真实的国产乱xxxx在线 | 国产精品久久久久久av公交车 | 日韩一区二区三区视频 | 国产嫩草91| 亚洲精品乱码久久久久久金桔影视 | 久久99精品久久久久久琪琪 | 日日夜夜视频 | 天堂av在线免费观看 | 国产在线精品视频 | 久久久久国产 | 婷婷天堂 | 国产精品久久久久久久一区探花 | 日韩精品影院 | 欧美日韩在线观看一区二区 | 国产欧美一区二区精品性色 | 日韩欧美一区二区中文字幕 | 久久久久久亚洲精品 | 久久国|