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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

关于Java和Scala同步的五件事你不知道

發布時間:2023/12/3 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于Java和Scala同步的五件事你不知道 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

實際上,所有服務器應用程序都需要在多個線程之間進行某種同步。 大多數同步工作是在框架級別為我們完成的,例如通過我們的Web服務器,數據庫客戶端或消息傳遞框架。 Java和Scala提供了許多組件來編寫可靠的多線程應用程序。 這些包括對象池,并發集合,高級鎖,執行上下文等。

為了更好地理解它們,讓我們探索最同步的習慣用法-Object lock 。 這種機制為synced關鍵字提供了動力,使其成為Java中最流行的多線程習慣用法之一(如果不是)。 它也是我們使用的許多更復雜模式的基礎,例如線程和連接池,并發集合等等。

synced關鍵字在兩個主要上下文中使用:

  • 作為方法修飾符,用于標記一種方法,該方法一次只能由一個線程執行。
  • 通過將代碼塊聲明為關鍵部分 –在任何給定時間點僅一個線程可以使用一個代碼塊。
  • 鎖定說明

    事實1 。 同步代碼塊使用兩個專用字節碼指令實現,這是官方規范的一部分-MonitorEnterMonitorExit 。 這與其他鎖定機制不同,例如在java.util.concurrent包中找到的那些鎖定機制,這些鎖定機制是結合Java代碼和通過sun.misc.Unsafe進行的本機調用實現的(對于HotSpot而言)。

    這些指令對開發人員在同步塊的上下文中明確指定的對象進行操作。 對于同步方法,鎖定將自動選擇為“ this ”變量。 對于靜態方法,鎖將放置在Class對象上。

    同步方法有時會導致不良行為 。 一個示例是在相同對象的不同同步方法之間創建隱式依賴關系,因為它們共享相同的鎖。 更糟糕的情況是在基類(甚至可能是第三方類)中聲明同步方法,然后將新的同步方法添加到派生類。 這會在整個層次結構中創建隱式同步依賴關系,并有可能導致吞吐量問題甚至死鎖。 為避免這些情況,建議使用私有對象作為鎖,以防止意外共享或逃脫鎖。

    編譯器和同步

    有兩個字節碼指令負責同步。 這是不尋常的,因為大多數字節碼指令彼此獨立,通常通過將值放在線程的操作數堆棧上來彼此“通信”。 還可以從操作數堆棧中加載要鎖定的對象,該操作數堆棧先前是通過取消引用變量,字段或調用返回對象的方法來放置的。

    事實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的原因之一。

    參考:我們的JCG合作伙伴 Tal Weiss在Takipi博客上對Java和Scala中的同步不了解的5件事 。

    翻譯自: https://www.javacodegeeks.com/2013/08/5-things-you-didnt-know-about-synchronization-in-java-and-scala.html

    總結

    以上是生活随笔為你收集整理的关于Java和Scala同步的五件事你不知道的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。