Java虛擬機(jī)是如何執(zhí)行線程同步的
想介紹下synchronized的原理,但是又不知道從何下手,在網(wǎng)上看到一篇老外的文章,介紹了和線程同步相關(guān)的幾個(gè)基礎(chǔ)知識點(diǎn)。所以想把它翻譯一下給大家看看。相信看過這些基礎(chǔ)知識之后再看我后面要寫的synchronized的原理就會好理解一點(diǎn)了。
了解Java語言的人都知道,Java代碼要想被JVM執(zhí)行,需要被轉(zhuǎn)換成由字節(jié)碼組成的class文件。本文主要來分析下Java虛擬機(jī)是如何在字節(jié)碼層面上執(zhí)行線程同步的。
線程和共享數(shù)據(jù)
Java編程語言的優(yōu)點(diǎn)之一是它在語言層面上對多線程的支持。這種支持大部分集中在協(xié)調(diào)多個(gè)線程對共享數(shù)據(jù)的訪問上。JVM的內(nèi)存結(jié)構(gòu)主要包含以下幾個(gè)重要的區(qū)域:棧、堆、方法區(qū)等。
在Java虛擬中,每個(gè)線程獨(dú)享一塊棧內(nèi)存,其中包括局部變量、線程調(diào)用的每個(gè)方法的參數(shù)和返回值。其他線程無法讀取到該棧內(nèi)存塊中的數(shù)據(jù)。棧中的數(shù)據(jù)僅限于基本類型和對象引用。所以,在JVM中,棧上是無法保存真實(shí)的對象的,只能保存對象的引用。真正的對象要保存在堆中。
在JVM中,堆內(nèi)存是所有線程共享的。堆中只包含對象,沒有其他東西。所以,堆上也無法保存基本類型和對象引用。堆和棧分工明確。但是,對象的引用其實(shí)也是對象的一部分。這里值得一提的是,數(shù)組是保存在堆上面的,即使是基本類型的數(shù)據(jù),也是保存在堆中的。因?yàn)樵贘ava中,數(shù)組是對象。
除了棧和堆,還有一部分?jǐn)?shù)據(jù)可能保存在JVM中的方法區(qū)中,比如類的靜態(tài)變量。方法區(qū)和棧類似,其中只包含基本類型和對象應(yīng)用。和棧不同的是,方法區(qū)中的靜態(tài)變量可以被所有線程訪問到。
對象和類的鎖
如前文提到,JVM中有兩塊內(nèi)存區(qū)域可以被所有線程共享:
- 堆,上面存放著所有對象
- 方法區(qū),上面存放著靜態(tài)變量
那么,如果有多個(gè)線程想要同時(shí)訪問同一個(gè)對象或者靜態(tài)變量,就需要被管控,否則可能出現(xiàn)不可預(yù)期的結(jié)果。
為了協(xié)調(diào)多個(gè)線程之間的共享數(shù)據(jù)訪問,虛擬機(jī)給每個(gè)對象和類都分配了一個(gè)鎖。這個(gè)鎖就像一個(gè)特權(quán),在同一時(shí)刻,只有一個(gè)線程可以“擁有”這個(gè)類或者對象。如果一個(gè)線程想要獲得某個(gè)類或者對象的鎖,需要詢問虛擬機(jī)。當(dāng)一個(gè)線程向虛擬機(jī)申請某個(gè)類或者對象的鎖之后,也許很快或者也許很慢虛擬機(jī)可以把鎖分配給這個(gè)線程,同時(shí)這個(gè)線程也許永遠(yuǎn)也無法獲得鎖。當(dāng)線程不再需要鎖的時(shí)候,他再把鎖還給虛擬機(jī)。這時(shí)虛擬機(jī)就可以再把鎖分配給其他申請鎖的線程。
類鎖其實(shí)通過對象鎖實(shí)現(xiàn)的。因?yàn)楫?dāng)虛擬機(jī)加載一個(gè)類的時(shí)候,會會為這個(gè)類實(shí)例化一個(gè) java.lang.Class 對象,當(dāng)你鎖住一個(gè)類的時(shí)候,其實(shí)鎖住的是其對應(yīng)的Class 對象。
監(jiān)視器(Monitors)
監(jiān)視器和鎖同時(shí)被JVM使用(我理解作者的意思應(yīng)該是想說鎖其實(shí)是通過監(jiān)視器實(shí)現(xiàn)的。),監(jiān)視器主要功能是監(jiān)控一段代碼,確保在同一時(shí)間只有一個(gè)線程在執(zhí)行。
每個(gè)監(jiān)視器都與一個(gè)對象相關(guān)聯(lián)。當(dāng)線程執(zhí)行到監(jiān)視器監(jiān)視下的代碼塊中的***條指令時(shí),線程必須獲取對被引用對象的鎖定。在線程獲取鎖之前,他是無法執(zhí)行這段代碼的,一旦獲得鎖,線程便可以進(jìn)入“被保護(hù)”的代碼開始執(zhí)行。
當(dāng)線程離開代碼塊的時(shí)候,無論如何離開,都會釋放所關(guān)聯(lián)對象的鎖。
多次加鎖
同一個(gè)線程可以對同一個(gè)對象進(jìn)行多次加鎖。每個(gè)對象維護(hù)著一個(gè)記錄著被鎖次數(shù)的計(jì)數(shù)器。未被鎖定的對象的該計(jì)數(shù)器為0,當(dāng)一個(gè)線程獲得鎖后,該計(jì)數(shù)器自增變?yōu)?1 ,當(dāng)同一個(gè)線程再次獲得該對象的鎖的時(shí)候,計(jì)數(shù)器再次自增。當(dāng)同一個(gè)線程釋放鎖的時(shí)候,計(jì)數(shù)器再自減。當(dāng)計(jì)數(shù)器為0的時(shí)候。鎖將被釋放,其他線程便可以獲得鎖。
同步
在Java中,當(dāng)有多個(gè)線程都必須要對同一個(gè)共享數(shù)據(jù)進(jìn)行訪問時(shí),有一種協(xié)調(diào)方式叫做同步。Java語言提供了兩種內(nèi)置方式來使線程同步的訪問數(shù)據(jù):同步代碼塊和同步方法。
這篇文章中后面還介紹了同步代碼塊和同步方法,以及簡單的介紹了下實(shí)現(xiàn)方式。這里就不做翻譯了,因?yàn)槲矣X得他介紹的太簡單了。我后面專門寫篇文章詳細(xì)介紹。
原文地址:How the Java virtual machine performs thread synchronization
【本文是51CTO專欄作者Hollis的原創(chuàng)文章,作者微信公眾號Hollis(ID:hollischuang)】