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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理

發(fā)布時間:2025/1/21 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

共享鎖(S鎖)

又稱為讀鎖,可以查看但無法修改和刪除的一種數(shù)據(jù)鎖。如果事務(wù)T對數(shù)據(jù)A加上共享鎖后,則其他事務(wù)只能對A再加共享鎖,不能加排它鎖。獲準(zhǔn)共享鎖的事務(wù)只能讀數(shù)據(jù),不能修改數(shù)據(jù)。 共享鎖下其它用戶可以并發(fā)讀取,查詢數(shù)據(jù)。但不能修改,增加,刪除數(shù)據(jù)。資源共享.

排它鎖(X鎖)

又稱為寫鎖、獨(dú)占鎖,若事務(wù)T對數(shù)據(jù)對象A加上X鎖,則只允許T讀取和修改A,其他任何事務(wù)都不能再對A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A

互斥鎖

在編程中,引入了對象互斥鎖的概念,來保證共享數(shù)據(jù)操作的完整性。每個對象都對應(yīng)于一個可稱為" 互斥鎖" 的標(biāo)記,這個標(biāo)記用來保證在任一時刻,只能有一個線程訪問該對象。

悲觀鎖

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

樂觀鎖

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

行級鎖

行級鎖是 MySQL 中鎖定粒度最細(xì)的一種鎖,表示只針對當(dāng)前操作的行進(jìn)行加鎖。行級鎖能大大減少數(shù)據(jù)庫操作的沖突,其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為共享鎖和排他鎖。開銷大,加鎖慢;會出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。

表級鎖

表級鎖是 MySQL 中鎖定粒度最大的一種鎖,表示對當(dāng)前操作的整張表加鎖,它實(shí)現(xiàn)簡單,資源消耗較少,被大部分 MySQL 引擎支持。最常使用的 MyISAM 與 InnoDB 都支持表級鎖定。表級鎖定分為表共享讀鎖(共享鎖)與表獨(dú)占寫鎖(排他鎖)。開銷小,加鎖快;不會出現(xiàn)死鎖;鎖定粒度大,發(fā)出鎖沖突的概率最高,并發(fā)度最低。

頁級鎖

頁級鎖是 MySQL 中鎖定粒度介于行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但沖突多,行級沖突少,但速度慢。因此,采取了折衷的頁級鎖,一次鎖定相鄰的一組記錄。BDB 支持頁級鎖。開銷和加鎖時間界于表鎖和行鎖之間;會出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。

丟失修改

指事務(wù)1和事務(wù)2同時讀入相同的數(shù)據(jù)并進(jìn)行修改,事務(wù)2提交的結(jié)果破壞了事務(wù)1提交的結(jié)果,導(dǎo)致事務(wù)1進(jìn)行的修改丟失。

事務(wù)

不可重復(fù)讀

一個事務(wù)在讀取某些數(shù)據(jù)后的某個時間,再次讀取以前讀過的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!

讀臟數(shù)據(jù)

事務(wù)T1修改某一數(shù)據(jù),并將其寫回磁盤,事務(wù)T2讀取同一數(shù)據(jù)后,T1由于某種原因被撤消,這時T1已修改過的數(shù)據(jù)恢復(fù)原值,T2讀到的數(shù)據(jù)就與數(shù)據(jù)庫中的數(shù)據(jù)不一致,則T2讀到的數(shù)據(jù)就為"臟"數(shù)據(jù),即不正確的數(shù)據(jù)。

死鎖

兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程

死鎖四個產(chǎn)生條件

1)互斥條件

指進(jìn)程對所分配到的資源進(jìn)行排它性使用,即在一段時間內(nèi)某資源只由一個進(jìn)程占用。如果此時還有其它進(jìn)程請求資源,則請求者只能等待,直至占有資源的進(jìn)程用畢釋放。

2)請求和保持條件

指進(jìn)程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進(jìn)程占有,此時請求進(jìn)程阻塞,但又對自己已獲得的其它資源保持不放。

3)不剝奪條件

指進(jìn)程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

4)環(huán)路等待條件

指在發(fā)生死鎖時,必然存在一個進(jìn)程——資源的環(huán)形鏈,即進(jìn)程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。

預(yù)防死鎖打破上述之一的條件。

CAS

CAS(Compare and Swap 比較并交換)是樂觀鎖的一種實(shí)現(xiàn)方式

鎖存在的問題

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

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

  • 在多線程競爭下,加鎖、釋放鎖會導(dǎo)致比較多的上下文切換和調(diào)度延時,引起性能問題。

  • 一個線程持有鎖會導(dǎo)致其它所有需要此鎖的線程掛起。

  • 如果一個優(yōu)先級高的線程等待一個優(yōu)先級低的線程釋放鎖會導(dǎo)致優(yōu)先級倒置,引起性能風(fēng)險。

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

    樂觀鎖

    其實(shí)就是一種思想。相對悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會產(chǎn)生并發(fā)沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時候,才會正式對數(shù)據(jù)是否產(chǎn)生并發(fā)沖突進(jìn)行檢測,如果發(fā)現(xiàn)并發(fā)沖突了,則返回用戶錯誤的信息,讓用戶決定如何去做。上面提到的樂觀鎖的概念中其實(shí)已經(jīng)闡述了它的具體實(shí)現(xiàn)細(xì)節(jié):主要就是兩個步驟:**沖突檢測和數(shù)據(jù)更新。**其實(shí)現(xiàn)方式有一種比較典型的就是 Compare and Swap ( CAS )。

    CAS介紹

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

    CAS 操作中包含三個操作數(shù) —— 需要讀寫的內(nèi)存位置(V)、進(jìn)行比較的預(yù)期原值(A)和擬寫入的新值(B)。如果內(nèi)存位置V的值與預(yù)期原值A(chǔ)相匹配,那么處理器會自動將該位置值更新為新值B。否則處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當(dāng)前值。)

    CAS 有效地說明了“ 我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現(xiàn)在的值即可。 ”這其實(shí)和樂觀鎖的沖突檢查+數(shù)據(jù)更新的原理是一樣的。

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

    JAVA對CAS的支持

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

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

    1 public class AtomicInteger extends Number implements java.io.Serializable { 2 private volatile int value; 3 4 public final int get() { 5 return value; 6 } 7 8 public final int getAndIncrement() { 9 for (;;) { 10 int current = get(); 11 int next = current + 1; 12 if (compareAndSet(current, next)) 13 return current; 14 } 15 } 16 17 public final boolean compareAndSet(int expect, int update) { 18 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 19 } 20 }

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

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

    而 compareAndSet 利用JNI(Java Native Interface)來完成CPU指令的操作:

    1 public final boolean compareAndSet(int expect, int update) { 2 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 3 } 

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

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

    那么比較this == expect,替換this = update,compareAndSwapInt實(shí)現(xiàn)這兩個步驟的原子性呢?

    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 public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);

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

    1 #define LOCK_IF_MP(mp) __asm cmp mp, 0 \2 __asm je L0 \3 __asm _emit 0xF0 \4 __asm L0:5 6 inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {7 // alternative for InterlockedCompareExchange8 int mp = os::is_MP();9 __asm { 10 mov edx, dest 11 mov ecx, exchange_value 12 mov eax, compare_value 13 LOCK_IF_MP(mp) 14 cmpxchg dword ptr [edx], ecx 15 } 16 }

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

    CAS缺點(diǎn)

    ABA問題

    CAS看起來很爽,但是會導(dǎo)致“ABA問題”。

    CAS算法實(shí)現(xiàn)一個重要前提需要取出內(nèi)存中某時刻的數(shù)據(jù),而在下時刻比較并替換,那么在這個時間差類會導(dǎo)致數(shù)據(jù)的變化。

    比如說一個線程one從內(nèi)存位置V中取出A,這時候另一個線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果鏈表的頭在變化了兩次后恢復(fù)了原值,但是不代表鏈表就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進(jìn)行原子操作。

    在運(yùn)用CAS做Lock-Free操作中有一個經(jīng)典的ABA問題:

    線程1準(zhǔn)備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然后線程1執(zhí)行CAS時發(fā)現(xiàn)變量的值仍然為A,所以CAS成功。但實(shí)際上這時的現(xiàn)場已經(jīng)和最初不同了,盡管CAS成功,但可能存在潛藏的問題,例如下面的例子:

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

    head.compareAndSet(A,B);

    在T1執(zhí)行上面這條指令之前,線程T2介入,將A、B出棧,再pushD、C、A,此時堆棧結(jié)構(gòu)如下圖,而對象B此時處于游離狀態(tài):

    此時輪到線程T1執(zhí)行CAS操作,檢測發(fā)現(xiàn)棧頂仍為A,所以CAS成功,棧頂變?yōu)锽,但實(shí)際上B.next為null,所以此時的情況變?yōu)?#xff1a;

    其中堆棧中只有B一個元素,C和D組成的鏈表不再存在于堆棧中,平白無故就把C、D丟掉了。

    以上就是由于ABA問題帶來的隱患,各種樂觀鎖的實(shí)現(xiàn)中通常都會用版本戳version來對記錄或?qū)ο髽?biāo)記,避免并發(fā)操作帶來的問題,在Java中,AtomicStampedReference也實(shí)現(xiàn)了這個作用,它通過包裝[E,Integer]的元組來對對象標(biāo)記版本戳stamp,從而避免ABA問題,例如下面的代碼分別用AtomicInteger和AtomicStampedReference來對初始值為100的原子整型變量進(jìn)行更新,AtomicInteger會成功執(zhí)行CAS操作,而加上版本戳的AtomicStampedReference對于ABA問題會執(zhí)行CAS失敗:

    import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference;public class ABA {private static AtomicInteger atomicInt = new AtomicInteger(100);private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);public static void main(String[] args) throws InterruptedException {Thread intT1 = new Thread(new Runnable() {@Overridepublic void run() {atomicInt.compareAndSet(100, 101);atomicInt.compareAndSet(101, 100);}});Thread intT2 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}boolean c3 = atomicInt.compareAndSet(100, 101);System.out.println(c3); //true}});intT1.start();intT2.start();intT1.join();intT2.join();Thread refT1 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);}});Thread refT2 = new Thread(new Runnable() {@Overridepublic void run() {int stamp = atomicStampedRef.getStamp();System.out.println("before sleep : stamp = " + stamp); // stamp = 0try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);System.out.println(c3); //false}});refT1.start();refT2.start();}}

    循環(huán)時間長開銷大

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

    只能保證一個共享變量的原子操作

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

    CAS與Synchronized的使用情景

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

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

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

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

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

    1. A線程寫volatile變量,隨后B線程讀這個volatile變量。2. A線程寫volatile變量,隨后B線程用CAS更新這個volatile變量。3. A線程用CAS更新一個volatile變量,隨后B線程用CAS更新這個volatile變量。4. A線程用CAS更新一個volatile變量,隨后B線程讀這個volatile變量。    

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

    1. 首先,聲明共享變量為volatile;  2. 然后,使用CAS的原子條件更新來實(shí)現(xiàn)線程之間的同步;3. 同時,配合以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)示意圖如下:

    JVM中的CAS(堆中對象的分配)

    Java調(diào)用new object()會創(chuàng)建一個對象,這個對象會被分配到JVM的堆中。那么這個對象到底是怎么在堆中保存的呢?

    首先,new object()執(zhí)行的時候,這個對象需要多大的空間,其實(shí)是已經(jīng)確定的,因為java中的各種數(shù)據(jù)類型,占用多大的空間都是固定的(對其原理不清楚的請自行Google)。那么接下來的工作就是在堆中找出那么一塊空間用于存放這個對象。
    在單線程的情況下,一般有兩種分配策略:

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

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

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

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

  • TLAB:如果使用CAS其實(shí)對性能還是會有影響的,所以JVM又提出了一種更高級的優(yōu)化策略:每個線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖區(qū)(TLAB),線程內(nèi)部需要分配內(nèi)存時直接在TLAB上分配就行,避免了線程沖突。只有當(dāng)緩沖區(qū)的內(nèi)存用光需要重新分配內(nèi)存的時候才會進(jìn)行CAS操作分配更大的內(nèi)存空間。
    虛擬機(jī)是否使用TLAB,可以通過-XX:+/-UseTLAB參數(shù)來進(jìn)行配置(jdk5及以后的版本默認(rèn)是啟用TLAB的)。

  • 總結(jié)

    以上是生活随笔為你收集整理的详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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