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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

java 变量锁_一张图看透java的“锁”事

發(fā)布時(shí)間:2024/10/8 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 变量锁_一张图看透java的“锁”事 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java提供了種類豐富的鎖,每種鎖因其特性的不同,在適當(dāng)?shù)膱鼍跋履軌蛘宫F(xiàn)出非常高的效率。

Java中往往是按照是否含有某一特性來定義鎖,我們通過特性將鎖進(jìn)行分組歸類,再使用對比的方式進(jìn)行介紹,幫助大家更快捷的理解相關(guān)知識(shí)。下面給出本文內(nèi)容的總體分類目錄:

1. 樂觀鎖 VS 悲觀鎖

樂觀鎖與悲觀鎖是一種廣義上的概念,體現(xiàn)了看待線程同步的不同角度。在Java和數(shù)據(jù)庫中都有此概念對應(yīng)的實(shí)際應(yīng)用。

先說概念。對于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有別的線程來修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線程修改。Java中,synchronized關(guān)鍵字和Lock的實(shí)現(xiàn)類都是悲觀鎖。

而樂觀鎖認(rèn)為自己在使用數(shù)據(jù)時(shí)不會(huì)有別的線程修改數(shù)據(jù),所以不會(huì)添加鎖,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒有別的線程更新了這個(gè)數(shù)據(jù)。如果這個(gè)數(shù)據(jù)沒有被更新,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫入。如果數(shù)據(jù)已經(jīng)被其他線程更新,則根據(jù)不同的實(shí)現(xiàn)方式執(zhí)行不同的操作(例如報(bào)錯(cuò)或者自動(dòng)重試)。

樂觀鎖在Java中是通過使用無鎖編程來實(shí)現(xiàn),最常采用的是CAS算法,Java原子類中的遞增操作就通過CAS自旋實(shí)現(xiàn)的。

根據(jù)從上面的概念描述我們可以發(fā)現(xiàn):

  • 悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確。
  • 樂觀鎖適合讀操作多的場景,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。

光說概念有些抽象,我們來看下樂觀鎖和悲觀鎖的調(diào)用方式示例:

// ------------------------- 悲觀鎖的調(diào)用方式 -------------------------// synchronizedpublic synchronized void testMethod() {// 操作同步資源}// ReentrantLockprivate ReentrantLock lock = new ReentrantLock(); // 需要保證多個(gè)線程使用的是同一個(gè)鎖public void modifyPublicResources() {lock.lock();// 操作同步資源lock.unlock();}// ------------------------- 樂觀鎖的調(diào)用方式 -------------------------private AtomicInteger atomicInteger = new AtomicInteger(); // 需要保證多個(gè)線程使用的是同一個(gè)AtomicIntegeratomicInteger.incrementAndGet(); //執(zhí)行自增1

通過調(diào)用方式示例,我們可以發(fā)現(xiàn)悲觀鎖基本都是在顯式的鎖定之后再操作同步資源,而樂觀鎖則直接去操作同步資源。那么,為何樂觀鎖能夠做到不鎖定同步資源也可以正確的實(shí)現(xiàn)線程同步呢?我們通過介紹樂觀鎖的主要實(shí)現(xiàn)方式 “CAS” 的技術(shù)原理來為大家解惑。

CAS全稱 Compare And Swap(比較與交換),是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步。java.util.concurrent包中的原子類就是通過CAS來實(shí)現(xiàn)了樂觀鎖。

CAS算法涉及到三個(gè)操作數(shù):

  • 需要讀寫的內(nèi)存值 V。
  • 進(jìn)行比較的值 A。
  • 要寫入的新值 B。

當(dāng)且僅當(dāng) V 的值等于 A 時(shí),CAS通過原子方式用新值B來更新V的值(“比較+更新”整體是一個(gè)原子操作),否則不會(huì)執(zhí)行任何操作。一般情況下,“更新”是一個(gè)不斷重試的操作。

之前提到j(luò)ava.util.concurrent包中的原子類,就是通過CAS來實(shí)現(xiàn)了樂觀鎖,那么我們進(jìn)入原子類AtomicInteger的源碼,看一下AtomicInteger的定義:

feda866e.png

根據(jù)定義我們可以看出各屬性的作用:

  • unsafe: 獲取并操作內(nèi)存的數(shù)據(jù)。
  • valueOffset: 存儲(chǔ)value在AtomicInteger中的偏移量。
  • value: 存儲(chǔ)AtomicInteger的int值,該屬性需要借助volatile關(guān)鍵字保證其在線程間是可見的。

接下來,我們查看AtomicInteger的自增函數(shù)incrementAndGet()的源碼時(shí),發(fā)現(xiàn)自增函數(shù)底層調(diào)用的是unsafe.getAndAddInt()。但是由于JDK本身只有Unsafe.class,只通過class文件中的參數(shù)名,并不能很好的了解方法的作用,所以我們通過OpenJDK 8 來查看Unsafe的源碼:

// ------------------------- JDK 8 -------------------------// AtomicInteger 自增方法public final int incrementAndGet() { ?return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}?// Unsafe.classpublic final int getAndAddInt(Object var1, long var2, int var4) { ?int var5; ?do { ? ? ?var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); ?return var5;}?// ------------------------- OpenJDK 8 -------------------------// Unsafe.javapublic final int getAndAddInt(Object o, long offset, int delta) { ? int v; ? do { ? ? ? v = getIntVolatile(o, offset); ? } while (!compareAndSwapInt(o, offset, v, v + delta)); ? return v;}

根據(jù)OpenJDK 8的源碼我們可以看出,getAndAddInt()循環(huán)獲取給定對象o中的偏移量處的值v,然后判斷內(nèi)存值是否等于v。如果相等則將內(nèi)存值設(shè)置為 v + delta,否則返回false,繼續(xù)循環(huán)進(jìn)行重試,直到設(shè)置成功才能退出循環(huán),并且將舊值返回。整個(gè)“比較+更新”操作封裝在compareAndSwapInt()中,在JNI里是借助于一個(gè)CPU指令完成的,屬于原子操作,可以保證多個(gè)線程都能夠看到同一個(gè)變量的修改值。

后續(xù)JDK通過CPU的cmpxchg指令,去比較寄存器中的 A 和 內(nèi)存中的值 V。如果相等,就把要寫入的新值 B 存入內(nèi)存中。如果不相等,就將內(nèi)存值 V 賦值給寄存器中的值 A。然后通過Java代碼中的while循環(huán)再次調(diào)用cmpxchg指令進(jìn)行重試,直到設(shè)置成功為止。

CAS雖然很高效,但是它也存在三大問題,這里也簡單說一下:

  • ABA問題。CAS需要在操作值的時(shí)候檢查內(nèi)存值是否發(fā)生變化,沒有發(fā)生變化才會(huì)更新內(nèi)存值。但是如果內(nèi)存值原來是A,后來變成了B,然后又變成了A,那么CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)值沒有發(fā)生變化,但是實(shí)際上是有變化的。ABA問題的解決思路就是在變量前面添加版本號(hào),每次變量更新的時(shí)候都把版本號(hào)加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,具體操作封裝在compareAndSet()中。compareAndSet()首先檢查當(dāng)前引用和當(dāng)前標(biāo)志與預(yù)期引用和預(yù)期標(biāo)志是否相等,如果都相等,則以原子方式將引用值和標(biāo)志的值設(shè)置為給定的更新值。
  • 循環(huán)時(shí)間長開銷大。CAS操作如果長時(shí)間不成功,會(huì)導(dǎo)致其一直自旋,給CPU帶來非常大的開銷。
  • 只能保證一個(gè)共享變量的原子操作。對一個(gè)共享變量執(zhí)行操作時(shí),CAS能夠保證原子操作,但是對多個(gè)共享變量操作時(shí),CAS是無法保證操作的原子性的。Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行CAS操作。
  • 下邊文章介紹:自旋鎖 VS 適應(yīng)性自旋鎖

    原創(chuàng)不易,期待你的關(guān)注

    總結(jié)

    以上是生活随笔為你收集整理的java 变量锁_一张图看透java的“锁”事的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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