scala和java像不像_关于Java和Scala同步的五件事你不知道
scala和java像不像
實(shí)際上,所有服務(wù)器應(yīng)用程序都需要在多個線程之間進(jìn)行某種同步。 大多數(shù)同步工作是在框架級別為我們完成的,例如通過我們的Web服務(wù)器,數(shù)據(jù)庫客戶端或消息傳遞框架。 Java和Scala提供了許多組件來編寫可靠的多線程應(yīng)用程序。 這些包括對象池,并發(fā)集合,高級鎖,執(zhí)行上下文等。
為了更好地理解它們,讓我們探索最同步的習(xí)慣用法-Object lock 。 這種機(jī)制為synced關(guān)鍵字提供了動力,使其成為Java中最流行的多線程習(xí)慣用法之一(如果不是)。 它也是我們使用的許多更復(fù)雜的模式的基礎(chǔ),例如線程和連接池,并發(fā)集合等等。
synced關(guān)鍵字在兩個主要上下文中使用:
鎖定說明
事實(shí)1 。 同步代碼塊使用兩條專用字節(jié)碼指令實(shí)現(xiàn),這是官方規(guī)范的一部分-MonitorEnter和MonitorExit 。 這與其他鎖定機(jī)制(例如在java.util.concurrent包中找到的鎖定機(jī)制)不同,該鎖定機(jī)制是結(jié)合Java代碼和通過sun.misc.Unsafe進(jìn)行的本機(jī)調(diào)用來實(shí)現(xiàn)的(對于HotSpot而言)。
這些指令對開發(fā)人員在同步塊的上下文中明確指定的對象進(jìn)行操作。 對于同步方法,鎖定將自動選擇為“ this ”變量。 對于靜態(tài)方法,鎖將放置在Class對象上。
同步方法有時會導(dǎo)致不良行為 。 一個示例是在相同對象的不同同步方法之間創(chuàng)建隱式依賴關(guān)系,因?yàn)樗鼈児蚕硐嗤逆i。 更糟糕的情況是在基類(甚至可能是第3方類)中聲明同步方法,然后將新的同步方法添加到派生類。 這會在整個層次結(jié)構(gòu)中創(chuàng)建隱式同步依賴關(guān)系,并有可能導(dǎo)致吞吐量問題甚至死鎖。 為避免這些情況,建議使用私有對象作為鎖,以防止意外共享或逃脫鎖。
編譯器和同步
有兩個字節(jié)碼指令負(fù)責(zé)同步。 這是不尋常的,因?yàn)榇蠖鄶?shù)字節(jié)碼指令彼此獨(dú)立,通常通過將值放在線程的操作數(shù)堆棧上來彼此“通信”。 還可以從操作數(shù)堆棧中加載要鎖定的對象,該操作數(shù)堆棧先前是通過取消引用變量,字段或調(diào)用返回對象的方法來放置的。
事實(shí)#2。 那么,如果調(diào)用這兩個指令之一而沒有分別調(diào)用另一個指令會發(fā)生什么呢? 如果不調(diào)用MonitorEnter,Java編譯器將不會生成調(diào)用MonitorExit的代碼。 即使這樣,從JVM的角度來看,這樣的代碼也是完全有效的。 這種情況的結(jié)果是MonitorExit指令拋出IllegalMonitorStateException。
如果通過MonitorEnter獲得鎖但沒有通過對MonitorExit的相應(yīng)調(diào)用釋放鎖,將會發(fā)生更危險(xiǎn)的情況 。 在這種情況下,擁有該鎖的線程可能導(dǎo)致其他試圖獲取該鎖的線程無限期地阻塞。 值得注意的是,由于該鎖是可重入的,因此擁有該鎖的線程可能會繼續(xù)愉快地執(zhí)行,即使它再次到達(dá)并重新輸入相同的鎖也是如此。
這就是陷阱。 為了防止這種情況的發(fā)生,Java編譯器以這樣的方式生成匹配的輸入和退出指令:一旦執(zhí)行已進(jìn)入同步塊或方法,它就必須通過匹配的MonitorExit指令來處理同一對象。 可能會引起麻煩的一件事是,如果在關(guān)鍵部分內(nèi)引發(fā)了異常。
public void hello() {synchronized (this) {System.out.println("Hi!, I'm alone here");} }讓我們分析一下字節(jié)碼–
aload_0 //load this into the operand stack dup //load it again astore_1 //backup this into an implicit variable stored at register 1 monitorenter //pop the value of this from the stack to enter the monitor//the actual critical section getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hi!, I'm alone here" invokevirtual java/io/PrintStream/println(Ljava/lang/String;)Vaload_1 //load the backup of this monitorexit //pop up the var and exit the monitor goto 14 // completed - jump to the end// the added catch clause - we got here if an exception was thrown - aload_1 // load the backup var. monitorexit //exit the monitor athrow // rethrow the exception object, loaded into the operand stack return編譯器用來防止棧不展開而無需通過MonitorExit指令的機(jī)制非常簡單–編譯器添加了一個隱式的try…catch子句以釋放鎖并重新拋出異常。
事實(shí)三 。 另一個問題是在相應(yīng)的enter和exit調(diào)用之間存儲的對鎖定對象的引用在哪里。 請記住,多個線程可能會使用不同的鎖對象同時執(zhí)行同一同步塊。 如果鎖定的對象是調(diào)用方法的結(jié)果,則JVM極不可能再次執(zhí)行它,因?yàn)樗赡軙膶ο蟮臓顟B(tài),甚至可能不會返回相同的對象。 對于自輸入監(jiān)視器以來可能已更改的變量或字段,情況也是如此。
監(jiān)視變量 。 為了解決這個問題,編譯器將一個隱式局部變量添加到方法中,以保存鎖定對象的值。 這是一個明智的解決方案,因?yàn)榕c使用并發(fā)堆結(jié)構(gòu)將鎖定對象映射到線程(該結(jié)構(gòu)本身可能需要同步)相比,該方法在維護(hù)對鎖定對象的引用上的開銷很小。 在構(gòu)建Takipi的堆棧分析算法時,我首先觀察到了這個新變量,并發(fā)現(xiàn)代碼中彈出了意外變量。
注意,所有這些工作都是在Java編譯器級別完成的。 JVM非常樂意通過MonitorEnter指令進(jìn)入關(guān)鍵部分而不退出(反之亦然),或?qū)⒉煌膶ο笥米鲗?yīng)的enter和exit方法。
鎖定在JVM級別
現(xiàn)在讓我們更深入地研究如何在JVM級別上實(shí)際實(shí)現(xiàn)鎖。 為此,我們將檢查HotSpot SE 7的實(shí)現(xiàn),因?yàn)檫@是VM特定的。 由于鎖定可能會對代碼吞吐量產(chǎn)生非常不利的影響,因此JVM進(jìn)行了一些非常強(qiáng)大的優(yōu)化,以盡可能高效地獲取和釋放鎖定。
事實(shí)#4。 JVM所采用的最強(qiáng)大的機(jī)制之一是線程鎖偏置 。 鎖定是每個Java對象都具有的固有功能,就像具有系統(tǒng)哈希碼或?qū)ζ涠x類的引用一樣。 無論對象的類型如何,都是如此(如果您愿意,甚至可以使用基本數(shù)組作為鎖)。
這些類型的數(shù)據(jù)存儲在每個對象的標(biāo)頭(也稱為對象的標(biāo)記)中。 保留在對象標(biāo)題中的某些數(shù)據(jù)保留用于描述對象的鎖定狀態(tài)。 這包括描述對象的鎖定狀態(tài)(即鎖定/解鎖)的位標(biāo)志,以及對當(dāng)前擁有該鎖的線程的引用-指向該對象的線程有偏。
為了節(jié)省對象標(biāo)頭中的空間,為了減小地址大小并節(jié)省每個對象標(biāo)頭中的位(64位和32位JVM為54位或23位),在VM堆的較低段中分配了Java線程對象。分別)。
對于64位–
鎖定算法
當(dāng)JVM嘗試獲取對象的鎖時,它會經(jīng)歷從樂觀到悲觀的一系列步驟。
事實(shí)五。 如果線程成功將其自身確立為對象鎖的所有者,則該線程將獲取該鎖。 這取決于線程是否能夠在對象的標(biāo)頭中安裝對自身的引用(指向內(nèi)部JavaThread對象的指針)。
獲取鎖。 使用簡單的比較和交換(CAS)操作即可完成此操作。 這非常有效,因?yàn)樗ǔ?梢赞D(zhuǎn)換為直接CPU指令(例如cmpxchg)。 CAS操作以及OS特定的線程駐留例程用作對象同步習(xí)慣用法的構(gòu)建塊。
如果該鎖是免費(fèi)的,或者先前已對該線程進(jìn)行了鎖定,則將獲得該線程的對象鎖,并且可以立即繼續(xù)執(zhí)行。 如果CAS失敗,則JVM將執(zhí)行一輪自旋鎖定,在該循環(huán)中線程停放以有效地使其在重試CAS之間進(jìn)入睡眠狀態(tài)。 如果這些初始嘗試失敗(向鎖發(fā)出更高級別的爭用信號),線程將自身進(jìn)入阻塞狀態(tài),并使其自身進(jìn)入爭用該鎖的線程列表,并開始一系列自旋鎖。
在每輪旋轉(zhuǎn)之后,線程將檢查JVM全局狀態(tài)的變化,例如“停止世界” GC的出現(xiàn),在這種情況下,線程將需要暫停自身直到GC完成以防止出現(xiàn)這種情況。在執(zhí)行STW GC時獲得鎖并繼續(xù)執(zhí)行的位置。
釋放鎖。 通過MonitorExit指令退出關(guān)鍵部分時,所有者線程將嘗試查看它是否可以喚醒任何正在等待釋放鎖的駐留線程。 此過程稱為選擇“繼承人”。 這是為了增加活動性,并防止在釋放鎖(也稱為絞合)的情況下保持線程停放的情況。
調(diào)試服務(wù)器多線程問題很困難,因?yàn)樗鼈兺Q于非常特定的時間安排和操作系統(tǒng)啟發(fā)。 這就是讓我們首先從事Takipi工作的原因之一。
翻譯自: https://www.javacodegeeks.com/2013/08/5-things-you-didnt-know-about-synchronization-in-java-and-scala.html
scala和java像不像
總結(jié)
以上是生活随笔為你收集整理的scala和java像不像_关于Java和Scala同步的五件事你不知道的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 力不暇供的意思 力不暇供出自哪
- 下一篇: 使用JDK 11在Java字符串上的新方