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