scala和java像不像_关于Java和Scala同步的五件事你不知道
scala和java像不像
實際上,所有服務器應用程序都需要在多個線程之間進行某種同步。 大多數同步工作是在框架級別為我們完成的,例如通過我們的Web服務器,數據庫客戶端或消息傳遞框架。 Java和Scala提供了許多組件來編寫可靠的多線程應用程序。 這些包括對象池,并發集合,高級鎖,執行上下文等。
為了更好地理解它們,讓我們探索最同步的習慣用法-Object lock 。 這種機制為synced關鍵字提供了動力,使其成為Java中最流行的多線程習慣用法之一(如果不是)。 它也是我們使用的許多更復雜的模式的基礎,例如線程和連接池,并發集合等等。
synced關鍵字在兩個主要上下文中使用:
鎖定說明
事實1 。 同步代碼塊使用兩條專用字節碼指令實現,這是官方規范的一部分-MonitorEnter和MonitorExit 。 這與其他鎖定機制(例如在java.util.concurrent包中找到的鎖定機制)不同,該鎖定機制是結合Java代碼和通過sun.misc.Unsafe進行的本機調用來實現的(對于HotSpot而言)。
這些指令對開發人員在同步塊的上下文中明確指定的對象進行操作。 對于同步方法,鎖定將自動選擇為“ this ”變量。 對于靜態方法,鎖將放置在Class對象上。
同步方法有時會導致不良行為 。 一個示例是在相同對象的不同同步方法之間創建隱式依賴關系,因為它們共享相同的鎖。 更糟糕的情況是在基類(甚至可能是第3方類)中聲明同步方法,然后將新的同步方法添加到派生類。 這會在整個層次結構中創建隱式同步依賴關系,并有可能導致吞吐量問題甚至死鎖。 為避免這些情況,建議使用私有對象作為鎖,以防止意外共享或逃脫鎖。
編譯器和同步
有兩個字節碼指令負責同步。 這是不尋常的,因為大多數字節碼指令彼此獨立,通常通過將值放在線程的操作數堆棧上來彼此“通信”。 還可以從操作數堆棧中加載要鎖定的對象,該操作數堆棧先前是通過取消引用變量,字段或調用返回對象的方法來放置的。
事實#2。 那么,如果調用這兩個指令之一而沒有分別調用另一個指令會發生什么呢? 如果不調用MonitorEnter,Java編譯器將不會生成調用MonitorExit的代碼。 即使這樣,從JVM的角度來看,這樣的代碼也是完全有效的。 這種情況的結果是MonitorExit指令拋出IllegalMonitorStateException。
如果通過MonitorEnter獲得鎖但沒有通過對MonitorExit的相應調用釋放鎖,將會發生更危險的情況 。 在這種情況下,擁有該鎖的線程可能導致其他試圖獲取該鎖的線程無限期地阻塞。 值得注意的是,由于該鎖是可重入的,因此擁有該鎖的線程可能會繼續愉快地執行,即使它再次到達并重新輸入相同的鎖也是如此。
這就是陷阱。 為了防止這種情況的發生,Java編譯器以這樣的方式生成匹配的輸入和退出指令:一旦執行已進入同步塊或方法,它就必須通過匹配的MonitorExit指令來處理同一對象。 可能會引起麻煩的一件事是,如果在關鍵部分內引發了異常。
public void hello() {synchronized (this) {System.out.println("Hi!, I'm alone here");} }讓我們分析一下字節碼–
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指令的機制非常簡單–編譯器添加了一個隱式的try…catch子句以釋放鎖并重新拋出異常。
事實三 。 另一個問題是在相應的enter和exit調用之間存儲的對鎖定對象的引用在哪里。 請記住,多個線程可能會使用不同的鎖對象同時執行同一同步塊。 如果鎖定的對象是調用方法的結果,則JVM極不可能再次執行它,因為它可能會更改對象的狀態,甚至可能不會返回相同的對象。 對于自輸入監視器以來可能已更改的變量或字段,情況也是如此。
監視變量 。 為了解決這個問題,編譯器將一個隱式局部變量添加到方法中,以保存鎖定對象的值。 這是一個明智的解決方案,因為與使用并發堆結構將鎖定對象映射到線程(該結構本身可能需要同步)相比,該方法在維護對鎖定對象的引用上的開銷很小。 在構建Takipi的堆棧分析算法時,我首先觀察到了這個新變量,并發現代碼中彈出了意外變量。
注意,所有這些工作都是在Java編譯器級別完成的。 JVM非常樂意通過MonitorEnter指令進入關鍵部分而不退出(反之亦然),或將不同的對象用作對應的enter和exit方法。
鎖定在JVM級別
現在讓我們更深入地研究如何在JVM級別上實際實現鎖。 為此,我們將檢查HotSpot SE 7的實現,因為這是VM特定的。 由于鎖定可能會對代碼吞吐量產生非常不利的影響,因此JVM進行了一些非常強大的優化,以盡可能高效地獲取和釋放鎖定。
事實#4。 JVM所采用的最強大的機制之一是線程鎖偏置 。 鎖定是每個Java對象都具有的固有功能,就像具有系統哈希碼或對其定義類的引用一樣。 無論對象的類型如何,都是如此(如果您愿意,甚至可以使用基本數組作為鎖)。
這些類型的數據存儲在每個對象的標頭(也稱為對象的標記)中。 保留在對象標題中的某些數據保留用于描述對象的鎖定狀態。 這包括描述對象的鎖定狀態(即鎖定/解鎖)的位標志,以及對當前擁有該鎖的線程的引用-指向該對象的線程有偏。
為了節省對象標頭中的空間,為了減小地址大小并節省每個對象標頭中的位(64位和32位JVM為54位或23位),在VM堆的較低段中分配了Java線程對象。分別)。
對于64位–
鎖定算法
當JVM嘗試獲取對象的鎖時,它會經歷從樂觀到悲觀的一系列步驟。
事實五。 如果線程成功將其自身確立為對象鎖的所有者,則該線程將獲取該鎖。 這取決于線程是否能夠在對象的標頭中安裝對自身的引用(指向內部JavaThread對象的指針)。
獲取鎖。 使用簡單的比較和交換(CAS)操作即可完成此操作。 這非常有效,因為它通常可以轉換為直接CPU指令(例如cmpxchg)。 CAS操作以及OS特定的線程駐留例程用作對象同步習慣用法的構建塊。
如果該鎖是免費的,或者先前已對該線程進行了鎖定,則將獲得該線程的對象鎖,并且可以立即繼續執行。 如果CAS失敗,則JVM將執行一輪自旋鎖定,在該循環中線程停放以有效地使其在重試CAS之間進入睡眠狀態。 如果這些初始嘗試失敗(向鎖發出更高級別的爭用信號),線程將自身進入阻塞狀態,并使其自身進入爭用該鎖的線程列表,并開始一系列自旋鎖。
在每輪旋轉之后,線程將檢查JVM全局狀態的變化,例如“停止世界” GC的出現,在這種情況下,線程將需要暫停自身直到GC完成以防止出現這種情況。在執行STW GC時獲得鎖并繼續執行的位置。
釋放鎖。 通過MonitorExit指令退出關鍵部分時,所有者線程將嘗試查看它是否可以喚醒任何正在等待釋放鎖的駐留線程。 此過程稱為選擇“繼承人”。 這是為了增加活動性,并防止在釋放鎖(也稱為絞合)的情況下保持線程停放的情況。
調試服務器多線程問題很困難,因為它們往往取決于非常特定的時間安排和操作系統啟發。 這就是讓我們首先從事Takipi工作的原因之一。
翻譯自: https://www.javacodegeeks.com/2013/08/5-things-you-didnt-know-about-synchronization-in-java-and-scala.html
scala和java像不像
總結
以上是生活随笔為你收集整理的scala和java像不像_关于Java和Scala同步的五件事你不知道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 力不暇供的意思 力不暇供出自哪
- 下一篇: 使用JDK 11在Java字符串上的新方