蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說(shuō)》
成都創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比阿圖什網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式阿圖什網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋阿圖什地區(qū)。費(fèi)用合理售后完善,十載實(shí)體公司更值得信賴。
提起Java領(lǐng)域中的鎖,是否有種“道不盡紅塵奢戀,訴不完人間恩怨“的”感同身受“之感?細(xì)數(shù)那些個(gè)“玩意兒”,你對(duì)Java的熱情是否還如初戀般“人生若只如初見(jiàn)”?
Java中對(duì)于鎖的實(shí)現(xiàn)真可謂是“百花齊放”,按照編程友好程度來(lái)說(shuō),美其名曰是Java提供了種類豐富的鎖,每種鎖因其特性的不同,在適當(dāng)?shù)膱?chǎng)景下能夠展現(xiàn)出非常高的效率。
但是,從理解的難度上來(lái)講,其類型錯(cuò)中復(fù)雜,主要原因是Java是按照是否含有某一特性來(lái)定義鎖的實(shí)現(xiàn),如果不能正確理解其含義,了解其特性的話,往往都會(huì)深陷其中,難可自拔。
查詢過(guò)很多技術(shù)資料與相關(guān)書籍,對(duì)其介紹真可謂是“模棱兩可”,生怕我們搞懂了似的,但是這也是我們無(wú)法繞過(guò)去的一個(gè)“坎坎”,除非有其他的選擇。
作為一名Java Developer來(lái)說(shuō),正確了解和掌握這些鎖的機(jī)制和原理,需要我們帶著一些實(shí)際問(wèn)題,通過(guò)特性將鎖進(jìn)行分組歸類,才能真正意義上理解和掌握。
比如,在Java領(lǐng)域中,針對(duì)于不同場(chǎng)景提供的鎖,都用于解決什么問(wèn)題?其實(shí)現(xiàn)方式是什么?各自又有什么特點(diǎn),對(duì)應(yīng)的應(yīng)用有哪些?
帶著這些問(wèn)題,今天我們就一起來(lái)盤一盤,Java領(lǐng)域中的鎖機(jī)制,盤點(diǎn)一下相關(guān)知識(shí)點(diǎn),以及不同的鎖的適用場(chǎng)景,幫助我們更快捷的理解和掌握這項(xiàng)必備技術(shù)奧義。
本文用到的一些關(guān)鍵詞語(yǔ)以及常用術(shù)語(yǔ),主要如下:
縱觀Java領(lǐng)域中“五花八門”的鎖,我們可以依據(jù)Java內(nèi)存模型的工作機(jī)制,來(lái)具體分析一下對(duì)應(yīng)問(wèn)題的提出和表現(xiàn),這也不失為打開Java領(lǐng)域中鎖機(jī)制的“敲門磚”。
從本質(zhì)上講,鎖是一種協(xié)調(diào)多個(gè)進(jìn)程 或者多個(gè)線程對(duì)某一個(gè)資源的訪問(wèn)的控制機(jī)制。
計(jì)算機(jī)運(yùn)行模型主要是描述計(jì)算機(jī)系統(tǒng)體系結(jié)構(gòu)的基本模型,一般主要是指CPU處理器結(jié)構(gòu)。
在計(jì)算機(jī)體系結(jié)構(gòu)中,中央處理器(CPU,Central Processing Unit)是一塊超大規(guī)模的集成電路,是一臺(tái)計(jì)算機(jī)的運(yùn)算核心(Core)和控制核心( Control Unit)。它的功能主要是解釋計(jì)算機(jī)指令以及處理計(jì)算機(jī)軟件中的數(shù)據(jù)。
一個(gè)計(jì)算能夠運(yùn)行起來(lái),主要是依靠CPU來(lái)負(fù)責(zé)執(zhí)行我們的輸入指令的,通常情況下,我們都把這些指令統(tǒng)稱為程序。
一般CPU決定著程序的運(yùn)行速度,可以看出CPU對(duì)程序的執(zhí)行有很重要的作用,但是一個(gè)計(jì)算機(jī)程序的運(yùn)行快慢并不是完全由CPU決定,除了CPU還有內(nèi)存、閃存等。
由此可見(jiàn),一個(gè)CPU主要由控制單元,算術(shù)邏輯單元和寄存器單元等3個(gè)部分組成。其中:
一般來(lái)說(shuō),寄存器單元是為了減少CPU對(duì)內(nèi)存的訪問(wèn)次數(shù),提升數(shù)據(jù)讀取性能而提出的,CPU中的寄存器單元主要分為通用寄存器和專用寄存器兩個(gè)種,其中:
簡(jiǎn)單來(lái)說(shuō),CPU與主存儲(chǔ)器主要是通過(guò)總線來(lái)進(jìn)行通信,CPU通過(guò)控制單元來(lái)操作主存中的數(shù)據(jù)。而CPU與其他設(shè)備的通信都是由控制來(lái)實(shí)現(xiàn)。
綜上所述,我們便可以得到一個(gè)計(jì)算機(jī)內(nèi)存模型的大致雛形,接下來(lái),我們便來(lái)一起盤點(diǎn)解析是計(jì)算機(jī)內(nèi)存模型的基本奧義。
計(jì)算機(jī)內(nèi)存模型一般是指計(jì)算系統(tǒng)底層與編程語(yǔ)言之間的約束規(guī)范,主要是描述計(jì)算機(jī)程序與共享存儲(chǔ)器訪問(wèn)的行為特征表現(xiàn)。
根據(jù)介紹計(jì)算機(jī)運(yùn)行模型來(lái)看,計(jì)算機(jī)內(nèi)存模型可以幫助以及指導(dǎo)我們理解Java內(nèi)存模型,主要在如下的兩個(gè)方面:
由此可見(jiàn),內(nèi)存模型用于定義處理器間的各層緩存與共享內(nèi)存的同步機(jī)制,以及線程與內(nèi)存之間交互的規(guī)則。
在操作系統(tǒng)層面,內(nèi)存主要可以分為物理內(nèi)存與虛擬內(nèi)存的概念,其中:
一般情況下,當(dāng)物理內(nèi)存不足時(shí),可以用虛擬內(nèi)存代替, 在虛擬內(nèi)存出現(xiàn)之前,程序?qū)ぶ酚玫亩际俏锢淼刂贰?/p>
從常見(jiàn)的存儲(chǔ)介質(zhì)來(lái)看,主要有:寄存器(Register),高速緩存(Cache),隨機(jī)存取存儲(chǔ)器(RAM),只讀存儲(chǔ)器(ROM)等4種,按照讀取快慢的順序是:Register>Cache>RAM>ROM。其中:
由于CPU的運(yùn)算速度比主存(物理內(nèi)存)的存取速度快很多,為了提高處理速度,現(xiàn)代CPU不直接和主存進(jìn)行通信,而是在CPU和主存之間設(shè)計(jì)了多層的Cache(高速緩存),越靠近CPU的高速緩存越快,容
量也越小。
按照數(shù)據(jù)讀取順序和與CPU內(nèi)核結(jié)合的緊密程度來(lái)看,大多數(shù)采用多層緩存策略,最經(jīng)典的就三層高速緩存架構(gòu)。
也就是我們常說(shuō)的,CPU高速緩存有L1和L2高速緩存(即一級(jí)高速緩存和二級(jí)緩存高速),部分高端CPU還具有L3高速緩存(即三級(jí)高速緩存):
CPU內(nèi)核讀取數(shù)據(jù)時(shí),先從L1高速緩存中讀取,如果沒(méi)有命中,再到L2、L3高速緩存中讀取,假如這些高速緩存都沒(méi)有命中,它就會(huì)到主存中讀取所需要的數(shù)據(jù)。
每一級(jí)高速緩存中所存儲(chǔ)的數(shù)據(jù)都是下一級(jí)高速緩存的一部分,越靠近CPU的高速緩存讀取越快,容量也越小。
當(dāng)然,系統(tǒng)還擁有一塊主存(即主內(nèi)存),由系統(tǒng)中的所有CPU共享。擁有L3高速緩存的CPU,CPU存取數(shù)據(jù)的命中率可達(dá)95%,也就是說(shuō)只有不到5%的數(shù)據(jù)需要從主存中去存取。
因此,高速緩存大大縮小了高速CPU內(nèi)核與低速主存之間的速度差距,基本體現(xiàn)在如下:
總結(jié)來(lái)說(shuō),CPU通過(guò)高速緩存進(jìn)行數(shù)據(jù)讀取有以下優(yōu)勢(shì):
綜上所述,一般來(lái)說(shuō),對(duì)于單線程程序,編譯器和處理器的優(yōu)化可以對(duì)編程開發(fā)足夠透明,對(duì)其優(yōu)化的效果不會(huì)影響結(jié)果的準(zhǔn)確性。
而在多線程程序來(lái)說(shuō),為了提升性能優(yōu)化的同時(shí)又達(dá)到兼顧執(zhí)行結(jié)果的準(zhǔn)確性,需要一定程度上內(nèi)存模型規(guī)范。
由于經(jīng)常會(huì)采用多層緩存策略,這就導(dǎo)致了一個(gè)比較經(jīng)典的并發(fā)編程三大問(wèn)題之一的共享變量的可見(jiàn)性問(wèn)題,除了可見(jiàn)性問(wèn)題之外,當(dāng)然還有原子性問(wèn)題和有序性問(wèn)題。
由此來(lái)看,在計(jì)算機(jī)內(nèi)存模型中,主要可以提出主存和工作內(nèi)存的概念,其中:
在Java領(lǐng)域中,為了解決這一系列問(wèn)題,特此提出了Java內(nèi)存模型,接下來(lái),我們就來(lái)一看看Java內(nèi)存模型的工作機(jī)制。
Java內(nèi)存模型主要是為了解決并發(fā)編程的可見(jiàn)性問(wèn)題,原子性問(wèn)題和有序性問(wèn)題等三大問(wèn)題,具有跨平臺(tái)性。
JMM最初由JSR-133(Java Memory Model and ThreadSpecification)文檔描述,JMM定義了一組規(guī)則或規(guī)范,該規(guī)范定義了一個(gè)線程對(duì)共享變量寫入時(shí),如何確保對(duì)另一個(gè)線程是可見(jiàn)的。
Java內(nèi)存模型(Java Memory Model JMM)指的是Java HotSpot(TM) VM 虛擬機(jī)定義的一種統(tǒng)一的內(nèi)存模型,將底層硬件以及操作系統(tǒng)的內(nèi)存訪問(wèn)差異進(jìn)行封裝,使得Java程序在不同硬件以及操作系統(tǒng)上執(zhí)行都能達(dá)到相同的并發(fā)效果。
Java內(nèi)存模型對(duì)于內(nèi)存的描述主要體現(xiàn)在三個(gè)方面:
一般來(lái)說(shuō),Java內(nèi)存模型在對(duì)內(nèi)存的描述上,我們可以依據(jù)是編譯時(shí)分配還是運(yùn)行時(shí)分配,是靜態(tài)分配還是動(dòng)態(tài)分配,是堆上分配還是棧上分配等角度來(lái)進(jìn)行對(duì)比分析。
從Java HotSpot(TM) VM 虛擬機(jī)的整體結(jié)構(gòu)上來(lái)看,內(nèi)存區(qū)域可以分為線程私有區(qū),線程共享區(qū),直接內(nèi)存等內(nèi)容,其中:
根據(jù)線程私有區(qū)中包含的數(shù)據(jù)(程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法區(qū))來(lái)具體分析看,其中:
根據(jù)線程共享區(qū)中包含的數(shù)據(jù)(JAVA 堆、方法區(qū))來(lái)具體分析看,其中:
這里對(duì)線程共享區(qū)和程私有區(qū)其細(xì)節(jié),就暫時(shí)不做展開,但是我們可以簡(jiǎn)單地看出,對(duì)于Java領(lǐng)域中的內(nèi)存分配,這兩者之間已經(jīng)幫助我們做了具體區(qū)分。
在繼續(xù)后續(xù)問(wèn)題探索之前,我們一起來(lái)思考一個(gè)問(wèn)題:按照線性思維來(lái)看,一個(gè)Java程序從程序編寫到編譯,編譯到運(yùn)行,運(yùn)行到執(zhí)行等過(guò)程來(lái)說(shuō),究竟是先入堆還是先入棧呢 ?
這個(gè)問(wèn)題,其實(shí)我在看Java HotSpot(TM) VM 虛擬機(jī)相關(guān)知識(shí)的時(shí)候,一直有這樣的個(gè)疑慮,但是其實(shí)這樣的表述是不準(zhǔn)確的,這需要結(jié)合編譯原理相關(guān)的知識(shí)來(lái)具體分析。
按照編譯原理的觀點(diǎn),從Java內(nèi)存分配策略來(lái)看,程序運(yùn)行時(shí)的內(nèi)存分配有三種策略,其中:
也就是說(shuō),在Java領(lǐng)域中,一個(gè)Java程序從程序編寫到編譯,編譯到運(yùn)行,運(yùn)行到執(zhí)行等過(guò)程來(lái)說(shuō),單純考慮是先入堆還是入棧的問(wèn)題,在這里得到了答案。
從整體上來(lái)看,Java內(nèi)存模型主要考慮的事情基本與主存,線程本地內(nèi)存,共享變量,變量副本,線程等概念息息相關(guān),其中:
在Java內(nèi)存模型中,一般來(lái)說(shuō)主要提供volatile,synchronized,final以及鎖等4種方式來(lái)保證變量的可見(jiàn)性問(wèn)題,其中:
實(shí)際上,相比之下,Java內(nèi)存模型還引入了一個(gè)工作內(nèi)存的概念來(lái)幫助我們提升性能,而且JMM提供了合理的禁用緩存以及禁止重排序的方法,所以其核心的價(jià)值在于解決可見(jiàn)性和有序性。
其中,需要特別注意的是,其主存和工作內(nèi)存的區(qū)別:
綜上所述,我們對(duì)Java內(nèi)存模型的探討算是水到渠成了,但是Java內(nèi)存模型也提出了一些規(guī)范,接下來(lái),我們就來(lái)看看Happen-Before 關(guān)系原則。
Java一致性模型指導(dǎo)原則是指制定一些規(guī)范來(lái)將復(fù)雜的物理計(jì)算機(jī)的系統(tǒng)底層封裝到JVM中,從而向上提供一種統(tǒng)一的內(nèi)存模型語(yǔ)義規(guī)則,一般是指Happens-Before規(guī)則。
Happen-Before 關(guān)系原則,是 Java 內(nèi)存模型中保證多線程操作可見(jiàn)性的機(jī)制,也是對(duì)早期語(yǔ)言規(guī)范中含糊的可見(jiàn)性概念的一個(gè)精確定義,其行為依賴于處理器本身的內(nèi)存一致性模型。
Happen-Before 關(guān)系原則主要規(guī)定了Java內(nèi)存在多線程操作下的順序性,一般是指先發(fā)生操作的執(zhí)行結(jié)果對(duì)后續(xù)發(fā)生的操作可見(jiàn),因此稱其為Java一致性模型指導(dǎo)原則。
由于Happen-Before 關(guān)系原則是向上提供一種統(tǒng)一的內(nèi)存模型語(yǔ)義規(guī)則,它規(guī)范了Java HotSpot(TM) VM 虛擬機(jī)的實(shí)現(xiàn),也能為上層Java Developer描述多線程并發(fā)的可見(jiàn)性問(wèn)題。
在Java領(lǐng)域中,Happen-Before 關(guān)系原則主要有8種,具體如下:
對(duì)于Happen-Before 關(guān)系原則來(lái)說(shuō),而不是簡(jiǎn)單地線性思維的前后順序問(wèn)題,是因?yàn)樗粌H僅是對(duì)執(zhí)行時(shí)間的保證,也包括對(duì)內(nèi)存讀、寫操作順序的保證。僅僅是時(shí)鐘順序上的先后,并不能保證線程交互的可見(jiàn)性。
在Java HotSpot(TM) VM 虛擬機(jī)內(nèi)部的運(yùn)行時(shí)數(shù)據(jù)區(qū),但是真正程序執(zhí)行,實(shí)際是要跑在具體的處理器內(nèi)核上。簡(jiǎn)單來(lái)說(shuō),把本地變量等數(shù)據(jù)從內(nèi)存加載到緩存、寄存器,然后運(yùn)算結(jié)束寫回主內(nèi)存。
總的來(lái)說(shuō),JMM 內(nèi)部的實(shí)現(xiàn)通常是依賴于內(nèi)存屏障,通過(guò)禁止某些重排序的方式,提供內(nèi)存可見(jiàn)性保證,也就是實(shí)現(xiàn)了各種 happen-before 規(guī)則。與此同時(shí),更多復(fù)雜度在于,需要盡量確保各種編譯器、各種體系結(jié)構(gòu)的處理器,都能夠提供一致的行為。
Java指令重排是指在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序的一種防護(hù)措施機(jī)制。
我們?cè)趯?shí)際開發(fā)工作中編寫代碼時(shí)候,是按照一定的代碼的思維和習(xí)慣去編排和組織代碼的,但是實(shí)際上,編譯器和CPU執(zhí)行的順序可能會(huì)代碼順序產(chǎn)生不一致的情況。
畢竟,編譯器和CPU會(huì)對(duì)我們編寫的程序代碼自身做一定程度上的優(yōu)化再去執(zhí)行,以此來(lái)提高執(zhí)行效率,因此提出了指令重排的機(jī)制。
一般來(lái)說(shuō),我們?cè)诔绦蛑芯帉懙拿恳粋€(gè)行代碼其實(shí)就是程序指令,按照線性思維方式來(lái)看,這些指令按道理是一行行代碼存在的順序去執(zhí)行的,只有上一行代碼執(zhí)行完畢,下一行代碼才會(huì)被執(zhí)行,這就說(shuō)明代碼的執(zhí)行有一定的順序。
但是這樣的順序,對(duì)于程序的執(zhí)行時(shí)間上來(lái)看是有一定的耗時(shí)的,為了加快代碼的執(zhí)行效率,一般會(huì)引入一種流水線技術(shù)的方式來(lái)解決這個(gè)問(wèn)題,就像Jenkins 流水線部署機(jī)制的編寫那樣。
但是流水線技術(shù)的本質(zhì)上,是把每一個(gè)指令拆成若干個(gè)部分,在同一個(gè)CPU的時(shí)間內(nèi)使其可以執(zhí)行多個(gè)指令的不同部分,從而達(dá)到提升執(zhí)行效率的目的,主要體現(xiàn)在:
一般來(lái)說(shuō),指令從排會(huì)涉及到CPU,編譯器,以及內(nèi)存等,因此指令重排序的類型大致可以分為 編譯器指令重排,CPU指令重排,內(nèi)存指令重排,其中:
在Java領(lǐng)域中,指令重排的原則是不能影響程序在單線程下的執(zhí)行的準(zhǔn)確性,但是在多線程的情況下,可能會(huì)導(dǎo)致程序執(zhí)行出現(xiàn)錯(cuò)誤的情況,主要是依據(jù)Happen-Before 關(guān)系原則來(lái)組織部重排序,其核心就是使用內(nèi)存屏障來(lái)實(shí)現(xiàn),通過(guò)內(nèi)存屏障可以堆內(nèi)存進(jìn)行順序約束,而且作用于線程。
由于Java有不同的編譯器和運(yùn)行時(shí)環(huán)境,對(duì)應(yīng)起來(lái)看,Java指令重排主要發(fā)生在編譯階段和運(yùn)行階段,而編譯階段對(duì)應(yīng)的是編譯器,運(yùn)行階段對(duì)應(yīng)著CPU,其中:
既然設(shè)置內(nèi)存屏障,可以確保多CPU的高速緩存中的數(shù)據(jù)與內(nèi)存保持一致性, 不能確保內(nèi)存與CPU緩存數(shù)據(jù)一致性的指令也不能重排,內(nèi)存屏障正是通過(guò)阻止屏障兩邊的指令重排序來(lái)避免編譯器和硬件的不正確優(yōu)化而提出的一種解決辦法。
但是內(nèi)存屏障的是需要考慮CPU的架構(gòu)方式,不同硬件實(shí)現(xiàn)內(nèi)存屏障的方式不同,一般以常見(jiàn)Intel CPU來(lái)看,主要有:
在Java領(lǐng)域中,Java內(nèi)存模型屏蔽了這種底層硬件平臺(tái)的差異,由JVM來(lái)為不同的平臺(tái)生成相應(yīng)的機(jī)器碼。
從廣義上的概念定義看,Java中的內(nèi)存屏障一般主要有Load和Store兩類:
從具體的使用方式來(lái)看,Java中的內(nèi)存屏障主要有以下幾種方式:
綜上所述,一般來(lái)說(shuō)volatile關(guān)健字能保證可見(jiàn)性和防止指令重排序,也是我們最常見(jiàn)提到的方式。
Java并發(fā)編程的三宗罪主要是指原子性問(wèn)題、可見(jiàn)性問(wèn)題和有序性問(wèn)題等三大問(wèn)題。
在介紹Java內(nèi)存模型時(shí),我們都說(shuō)其核心的價(jià)值在于解決可見(jiàn)性和有序性,以及還有原子性等,那么對(duì)其總結(jié)來(lái)說(shuō),就是Java并發(fā)編程的三宗罪,其中:
但是,這里我們需要知道,Java內(nèi)存模型是如何解決這些問(wèn)題的?主要體現(xiàn)如下幾個(gè)方面:
一定意義上來(lái)講,一般在Java并發(fā)編程中,其實(shí)加鎖可以解決一部分問(wèn)題,除此之外,我們還需要考慮線程饑餓問(wèn)題,數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,競(jìng)爭(zhēng)條件問(wèn)題以及死鎖問(wèn)題,通過(guò)綜合分析才能得到意想不到的結(jié)果。
綜上所述,我們?cè)诶斫釰ava領(lǐng)域中的鎖時(shí),可以以此作為一個(gè)考量標(biāo)準(zhǔn)之一,來(lái)幫助和方便我們更快理解和掌握并發(fā)編程技術(shù)。
Java線程饑餓問(wèn)題是指長(zhǎng)期無(wú)法獲取共享資源或搶占CPU資源而導(dǎo)致線程無(wú)法執(zhí)行的現(xiàn)象。
在Java并發(fā)編程的過(guò)程中,特別是開啟線程數(shù)過(guò)多,會(huì)遇到某些線程貪婪地把CPU資源占滿,導(dǎo)致某些線程分配不到CPU而沒(méi)有辦法執(zhí)行。
在Java領(lǐng)域中,對(duì)于線程饑餓問(wèn)題,可以從以下幾個(gè)方面來(lái)看:
針對(duì)上述的饑餓問(wèn)題,為了解決它,JDK內(nèi)部實(shí)現(xiàn)一些具備公平性質(zhì)的鎖,可以直接使用。所以,解決線程饑餓問(wèn)題,一般是引入隊(duì)列,也就是排隊(duì)處理,最典型的有ReentrantLock。
綜上所述,這不就是為我們掌握和理解Java中的鎖機(jī)制時(shí),需要考慮Java線程饑餓問(wèn)題。
Java數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題是指至少存在兩個(gè)線程去讀寫某個(gè)共享內(nèi)存,其中至少一個(gè)線程對(duì)其共享內(nèi)存進(jìn)行寫操作。
對(duì)于數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,最簡(jiǎn)單的理解就是,多個(gè)線程在同時(shí)對(duì)于共享內(nèi)存的進(jìn)行寫操作時(shí),在寫的過(guò)程中,其他的線程讀到數(shù)據(jù)是內(nèi)存數(shù)據(jù)中非正確預(yù)期的。
產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)的原因,一個(gè)CPU在任意時(shí)刻只能執(zhí)行一條指令,但是對(duì)其某個(gè)內(nèi)存中的寫操作可能會(huì)用到若干條件機(jī)器指令,從而導(dǎo)致在寫的過(guò)程中還沒(méi)完全修改完內(nèi)存,其他線程去讀取數(shù)據(jù),從而導(dǎo)致結(jié)果不可預(yù)知。從而引發(fā)數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,這個(gè)情況有點(diǎn)像MySQL數(shù)據(jù)中并發(fā)事務(wù)引起的臟讀情況。
在Java領(lǐng)域中,解決數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題的方式一般是把共享內(nèi)存的更新操作進(jìn)行原子化,同時(shí)也保證內(nèi)存的可見(jiàn)性。
針對(duì)上述的饑餓問(wèn)題,為了解決它,JDK內(nèi)部實(shí)現(xiàn)一系列的原子類,比如AtomicReference類等,但是主要可以采用CAS+自旋鎖的方式來(lái)實(shí)現(xiàn)。
綜上所述,這不就是為我們掌握和理解Java中的鎖機(jī)制時(shí),需要考慮Java數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
Java競(jìng)爭(zhēng)條件問(wèn)題是指代碼在執(zhí)行臨界區(qū)產(chǎn)生競(jìng)爭(zhēng)條件,主要是因?yàn)槎鄠€(gè)線程不同的執(zhí)行順序以及線程并發(fā)的交叉執(zhí)行導(dǎo)致執(zhí)行結(jié)果與預(yù)期不一致的情況。
對(duì)于競(jìng)爭(zhēng)條件問(wèn)題,其中臨界區(qū)是一塊代碼區(qū)域,其實(shí)說(shuō)白了就是我們自己寫的邏輯代碼,由于沒(méi)有考慮位,從而引發(fā)的多個(gè)線程不同的執(zhí)行順序以及線程并發(fā)的交叉執(zhí)行導(dǎo)致執(zhí)行結(jié)果與預(yù)期不一致的情況。
產(chǎn)生競(jìng)爭(zhēng)條件問(wèn)題的主要原因,一般主要有線程執(zhí)行順序的不確定性和并發(fā)機(jī)制導(dǎo)致上下文切換等兩個(gè)原因?qū)е赂?jìng)爭(zhēng)條件問(wèn)題,其中:
在Java領(lǐng)域中,解決競(jìng)爭(zhēng)條件問(wèn)題的方式一般是把臨界區(qū)進(jìn)行原子化,保證臨界區(qū)的源自性,保證了臨界區(qū)捏只有一個(gè)線程,從而避免競(jìng)爭(zhēng)產(chǎn)生。
針對(duì)上述的饑餓問(wèn)題,為了解決它,JDK內(nèi)部實(shí)現(xiàn)一系列的原子類或者說(shuō)直接使用synchronized來(lái)聲明,均可實(shí)現(xiàn)。
綜上所述,這不就是為我們掌握和理解Java中的鎖機(jī)制時(shí),需要考慮Java競(jìng)爭(zhēng)條件問(wèn)題。
Java死鎖問(wèn)題主要是指一種有兩個(gè)或者兩個(gè)以上的線程或者進(jìn)程構(gòu)成一個(gè)無(wú)限互相等待的環(huán)形狀態(tài)的情況,不是一種鎖概念,而是一種線程狀態(tài)的表征描述。
一般為了保證線程安全問(wèn)題,我們都會(huì)想著給會(huì)使用加鎖機(jī)制來(lái)確保線程安全,但如果過(guò)度地使用加鎖,則可能導(dǎo)致鎖順序死鎖(Lock-Ordering Deadlock)。
或者有的場(chǎng)景我們使用線程池和信號(hào)量等來(lái)限制資源的使用,但這些被限制的行為可能會(huì)導(dǎo)致資源死鎖(Resource DeadLock)。
Java死鎖問(wèn)題的主要體現(xiàn)在以下幾個(gè)方面:
當(dāng)然,死鎖問(wèn)題的產(chǎn)生也必須具備以及同時(shí)滿足以下幾個(gè)條件:
對(duì)于死鎖問(wèn)題,一般都是需要編程開發(fā)人員人為去干預(yù)和防止的,只是需要一些措施區(qū)規(guī)范處理,主要可以分為事前預(yù)防和事后處理等2種方式,其中:
除了有死鎖的問(wèn)題,當(dāng)然還有活鎖問(wèn)題,主要是因?yàn)槟承┻壿媽?dǎo)致一直在做無(wú)用功,使得線程無(wú)法正確執(zhí)行的情況。
在Java領(lǐng)域中,我們可以將鎖大致分為基于Java語(yǔ)法層面(關(guān)鍵詞)實(shí)現(xiàn)的鎖和基于JDK層面實(shí)現(xiàn)的鎖。
單純從Java對(duì)其實(shí)現(xiàn)的方式上來(lái)看,我們大體上可以將其分為基于Java語(yǔ)法層面(關(guān)鍵詞)實(shí)現(xiàn)的鎖和基于JDK層面實(shí)現(xiàn)的鎖。其中:
需要特別注意的是,在Java領(lǐng)域中,基于JDK層面的鎖通過(guò)CAS操作解決了并發(fā)編程中的原子性問(wèn)題,而基于Java語(yǔ)法層面實(shí)現(xiàn)的鎖解決了并發(fā)編程中的原子性問(wèn)題和可見(jiàn)性問(wèn)題。
單純從Java對(duì)其實(shí)現(xiàn)的方式上來(lái)看,我們大體上可以將其分為基于Java語(yǔ)法層面(關(guān)鍵詞)實(shí)現(xiàn)的鎖和基于JDK層面實(shí)現(xiàn)的鎖。其中:
需要特別注意的是,在Java領(lǐng)域中,基于JDK層面的鎖通過(guò)CAS操作解決了并發(fā)編程中的原子性問(wèn)題,而基于Java語(yǔ)法層面實(shí)現(xiàn)的鎖解決了并發(fā)編程中的原子性問(wèn)題和可見(jiàn)性問(wèn)題。
而從具體到對(duì)應(yīng)的Java線程資源來(lái)說(shuō),我們按照是否含有某一特性來(lái)定義鎖,主要可以從如下幾個(gè)方面來(lái)看:
針對(duì)于上述描述的各種情況,這里就不做展開和贅述,看到這里只需要在腦中形成一個(gè)概念就行,后續(xù)會(huì)有專門的內(nèi)容來(lái)對(duì)其進(jìn)行分析和探討。
在上述的內(nèi)容中,一般常規(guī)的概念中,我們很難會(huì)依據(jù)上述這些問(wèn)題去認(rèn)識(shí)和看待Java中的鎖機(jī)制,主要是在學(xué)習(xí)和查閱資料的時(shí),大多數(shù)的論調(diào)都是零散和細(xì)分的,很難在我們的腦海中形成知識(shí)體系。
從本質(zhì)上講,我們對(duì)鎖應(yīng)該有一個(gè)認(rèn)識(shí),其主要是一種協(xié)調(diào)多個(gè)進(jìn)程 或者多個(gè)線程對(duì)某一個(gè)資源的訪問(wèn)的控制機(jī)制,是并發(fā)編程中最關(guān)鍵的一環(huán)。
接下來(lái),對(duì)于上述內(nèi)容做一個(gè)簡(jiǎn)單的總結(jié):
單純從Java對(duì)其實(shí)現(xiàn)的方式上來(lái)看,我們大體上可以將其分為基于Java語(yǔ)法層面(關(guān)鍵詞)實(shí)現(xiàn)的鎖和基于JDK層面實(shí)現(xiàn)的鎖,其中:
綜上所述,我相信看到這里的時(shí)候,對(duì)Java領(lǐng)域中的鎖機(jī)制已經(jīng)有一個(gè)基本的輪廓,后面會(huì)專門寫一篇內(nèi)容來(lái)詳細(xì)介紹,敬請(qǐng)期待。
最后,技術(shù)研究之路任重而道遠(yuǎn),愿我們熬的每一個(gè)通宵,都撐得起我們想在這條路上走下去的勇氣,未來(lái)仍然可期,與君共勉!
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循相關(guān)版權(quán)協(xié)議,如若轉(zhuǎn)載或者分享請(qǐng)附上原文出處鏈接和鏈接來(lái)源。