摘要:并發(fā)需要解決的問題功能性問題線程同步面臨兩個(gè)問題,想象下有兩個(gè)線程在協(xié)作工作完成某項(xiàng)任務(wù)。鎖可用于規(guī)定一個(gè)臨界區(qū),同一時(shí)間臨界區(qū)內(nèi)僅能由一個(gè)線程訪問。并發(fā)的數(shù)據(jù)結(jié)構(gòu)線程安全的容器,如等。
并發(fā)指在宏觀上的同一時(shí)間內(nèi)同時(shí)執(zhí)行多個(gè)任務(wù)。為了滿足這一需求,現(xiàn)代的操作系統(tǒng)都抽象出 線程 的概念,供上層應(yīng)用使用。
這篇博文不打算詳細(xì)展開分析,而是對(duì)java并發(fā)中的概念和工具做一個(gè)梳理。
沿著并發(fā)模型、并發(fā)要解決的問題、基本工具、衍生工具這一思路展開。
首先線程是什么?線程是由OS抽象并實(shí)現(xiàn)的,我們知道OS的職責(zé)是管理并合理分配硬件資源,那么OS為了更好的管理、分配CPU資源,同時(shí)也為了滿足同時(shí)執(zhí)行任務(wù)這一需求,設(shè)計(jì)了線程這一概念。
雖然java程序運(yùn)行在JVM虛擬機(jī)上,但是java的線程仍然是對(duì)操作系統(tǒng)原生線程的封裝,同時(shí),jvm對(duì)線程實(shí)現(xiàn)時(shí)也將jvm的運(yùn)行棧設(shè)計(jì)成線程私有內(nèi)存,因此,java線程和原生線程在理解上實(shí)際上沒太大區(qū)別。
線程的五種狀態(tài):
graph LR 新建 --> 就緒; 就緒 --> 運(yùn)行; 運(yùn)行 --> 就緒; 運(yùn)行 --> 阻塞; 阻塞 --> 就緒; 運(yùn)行 --> 死亡;
先來(lái)看上面的就緒狀態(tài)和運(yùn)行狀態(tài)。我們知道線程雖然宏觀上是同時(shí)執(zhí)行的,但是微觀上使用如時(shí)間片輪轉(zhuǎn)算法使得線程依次執(zhí)行。那么,同一時(shí)間只有一個(gè)線程執(zhí)行,其它需要執(zhí)行的線程處于 就緒隊(duì)列 中,等待自己被調(diào)度到。
而如果線程想要暫時(shí)放棄在CPU上運(yùn)行的權(quán)利,就會(huì)阻塞自己。這時(shí)對(duì)應(yīng)著阻塞狀態(tài),同時(shí)線程會(huì)從就緒隊(duì)列中移除,進(jìn)入等待隊(duì)列。
很顯然,阻塞線程被喚醒肯定是進(jìn)入就緒隊(duì)列等待調(diào)度,而不可能是直接分配到CPU上運(yùn)行。
在線程同步時(shí),線程可能由于以下情況被阻塞:
同步阻塞。就是被鎖阻塞。
等待阻塞。被條件變量阻塞。
其它。調(diào)用sleep(), join()或等待IO操作時(shí)的阻塞。
并發(fā)需要解決的問題 功能性問題線程同步面臨兩個(gè)問題,想象下有兩個(gè)線程在協(xié)作工作完成某項(xiàng)任務(wù)。那么需要解決以下問題:
線程兩個(gè)線程之間交互數(shù)據(jù),必然涉及到數(shù)據(jù)共享。而某些數(shù)據(jù)資源無(wú)法被多個(gè)線程同時(shí)使用(臨界區(qū)),這時(shí)需要,即線程互斥問題。
假如一個(gè)線程進(jìn)行的太快,另外一個(gè)線程就需要等等它,即線程同步問題。
性能和可用性問題在多線程程序的性能問題上,如果是對(duì)于同樣一段臨界區(qū)的多線程訪問,那么則有以下幾個(gè)思路:
互斥鎖?;コ怄i即保證同一時(shí)間只有一個(gè)線程訪問臨界區(qū)并完整執(zhí)行完,其它線程在臨界區(qū)外面等待。
無(wú)障礙或無(wú)鎖。線程們一開始直接進(jìn)入臨界區(qū)執(zhí)行,注意其中不能修改共享數(shù)據(jù)。執(zhí)行完后再判斷剛才這段時(shí)間是否有其它線程執(zhí)行,沒有的話才修改共享數(shù)據(jù),如果有的話就回滾重來(lái)。
降低鎖粒度。也即將這個(gè)大的臨界區(qū)拆分成幾個(gè)小的臨界區(qū),分別加互斥鎖控制,這樣提高了線程同時(shí)訪問的臨界區(qū)的機(jī)會(huì)變多,性能提高。顯然這要對(duì)代碼仔細(xì)推敲,考慮如何拆分鎖粒度而不影響整體的語(yǔ)義。
以上三種思路的性能優(yōu)劣沒有一個(gè)普適的結(jié)果,和具體的場(chǎng)景相關(guān)。
并發(fā)中還會(huì)出現(xiàn)以下幾種情況導(dǎo)致系統(tǒng)不可用:
死鎖。不解釋。
饑餓。線程調(diào)度算法如果不是平等分配的,那么就可能出現(xiàn)優(yōu)先級(jí)高的線程長(zhǎng)時(shí)間占用CPU,導(dǎo)致優(yōu)先級(jí)低的線程無(wú)法得到執(zhí)行機(jī)會(huì)。
活鎖。這個(gè)我解釋不來(lái)。。。
并發(fā)代碼的幾個(gè)性質(zhì)并發(fā)編程中需要考慮的幾個(gè)概念:
原子性:指某個(gè)操作一旦被某個(gè)線程執(zhí)行,直到該操作執(zhí)行完畢都不會(huì)有其它線程來(lái)干擾。
可見性:指某個(gè)變量或某塊內(nèi)存如果被A線程修改,B線程能否馬上讀取到修改后的值。
有序性:A線程執(zhí)行的代碼序列,在B線程看來(lái)是否是有序的。
從我個(gè)人的理解來(lái)看,原子性屬于由并發(fā)和線程這一理論概念自然而然推導(dǎo)衍生而來(lái)的概念,而可見性和有序性是具體的工程實(shí)踐中產(chǎn)生的。
實(shí)際中,jvm并不能實(shí)現(xiàn)的特別完美,總會(huì)有工程上的妥協(xié)。理論模型與實(shí)際模型無(wú)法完美契合,總存在一定的偏差。
比如說(shuō),jvm為了向性能妥協(xié)使用了緩存機(jī)制,犧牲了數(shù)據(jù)一致性,這就產(chǎn)生了可見性的概念,需要程序員編程時(shí)自己控制。
jvm為了指令更高效率的執(zhí)行進(jìn)行了指令重排優(yōu)化,則產(chǎn)生了有序性的問題。印象里以前大學(xué)里學(xué)過的CPU的流水線技術(shù),為了指令能夠更好的被CPU流水線利用,減少流水線的空閑時(shí)間,編譯器編譯時(shí)也會(huì)在不影響 串行語(yǔ)義 的前提下,進(jìn)行指令重排。
總而言之,這是在性能和理論模型完整性之間的一種妥協(xié)。
技術(shù)上的工具、概念繁多復(fù)雜,但是如果我們能理解技術(shù)設(shè)計(jì)上無(wú)時(shí)無(wú)刻的不運(yùn)用抽象和分層的手段,
那么,我們可以把技術(shù)上的工具分為兩種:
最基本的、原生的工具。
在原生提供的工具上,進(jìn)行封裝得到的更高層次的工具。
更高層次的工具對(duì)基礎(chǔ)工具進(jìn)行了抽象和封裝,屏蔽了其中的實(shí)現(xiàn)細(xì)節(jié)。
這里想強(qiáng)調(diào)的是,工具的接口和實(shí)現(xiàn)是分開的,兩者可以沒有關(guān)系。
如java的監(jiān)視器鎖從接口上來(lái)看,其語(yǔ)義和互斥鎖一樣。然而它并不一定使用互斥鎖實(shí)現(xiàn),而是可以為了性能存在優(yōu)化,只要最終的行為與接口相同即可。
有三種用于線程同步的工具:
鎖。鎖可用于規(guī)定一個(gè) 臨界區(qū),同一時(shí)間臨界區(qū)內(nèi)僅能由一個(gè)線程訪問。其他線程則在臨界區(qū)外等待(阻塞)。
互斥鎖。使用信號(hào)量實(shí)現(xiàn)。臨界區(qū)外等待的線程會(huì)被阻塞。
自旋鎖。臨界區(qū)外等待的線程會(huì)忙等。
條件變量(Condition)。線程在某種條件不滿足時(shí)阻塞自己,等待其它的線程條件滿足時(shí)再喚醒它們。很顯然所有等待的線程要放入一個(gè)數(shù)據(jù)結(jié)構(gòu)中,這個(gè)數(shù)據(jù)結(jié)構(gòu)就在條件變量?jī)?nèi)。
信號(hào)量。操作系統(tǒng)原生的機(jī)制。實(shí)際上,鎖 + 條件變量可完成所有信號(hào)量可以完成的邏輯。
在java中,Object類有wait()、notify()和notifyAll()之類的方法。
這些方法可以認(rèn)為每個(gè)對(duì)象都內(nèi)置了一個(gè)條件變量,而這些方法是對(duì)這些條件變量的操作,因此,可以使用這些方法將對(duì)象當(dāng)作條件變量使用,從而做到線程的同步。
底層機(jī)制的特點(diǎn)直接得到的:
1. java中的volatile關(guān)鍵字。 2. CAS。
volatile關(guān)鍵字能夠保證變量的可見性,或者說(shuō)是讀或?qū)懙脑有浴?/p>
CAS即compareAndSwap,原子操作 。
CAS操作直接能夠?qū)?yīng)到單條CPU指令,因此天然具有原子性。java中是通過JNI調(diào)用C語(yǔ)言從而調(diào)用CPU底層指令實(shí)現(xiàn)。
CAS的行為和以下代碼一致:
int cas(long *addr, long old, long new) { if (*addr == old) { *addr = new; return 1; } else { return 0; //* } }
那么CAS可以做什么呢?很多樂觀并發(fā)控制可以基于CAS實(shí)現(xiàn)。
比如說(shuō),通過一個(gè)標(biāo)記變量來(lái)記錄臨界區(qū)被誰(shuí)占有,線程進(jìn)入臨界區(qū)前不斷的使用CAS操作判斷標(biāo)記變量是否為空同時(shí)將其記錄為自己,來(lái)實(shí)現(xiàn)鎖機(jī)制。這就是自旋鎖的思路。
除此之外,樂觀鎖也能用CAS實(shí)現(xiàn),比如java的Atomic系列,就是這樣實(shí)現(xiàn)的。
由基本工具封裝、優(yōu)化而成的衍生工具 synchronized關(guān)鍵字前面說(shuō)到可以認(rèn)為每個(gè)對(duì)象內(nèi)置一個(gè)條件變量,同樣,每個(gè)對(duì)象也內(nèi)置一個(gè)鎖。這個(gè)內(nèi)置鎖在Java中被抽象為監(jiān)視器鎖(monitor)。
synchronized關(guān)鍵字的使用實(shí)際上就相當(dāng)于使用監(jiān)視器鎖定義了一個(gè)臨界區(qū)。使用這種語(yǔ)法也特別直觀簡(jiǎn)單,所以java經(jīng)常用synchronizd來(lái)進(jìn)行線程的同步。
JDK1.6之后,為了提升監(jiān)視器鎖的性能,java通過某些手段進(jìn)行了優(yōu)化。其中包含鎖優(yōu)化機(jī)制,對(duì)應(yīng)三種鎖:
1. 偏向鎖 2. 輕量級(jí)鎖 3. 重量級(jí)鎖
一開始只有一個(gè)線程使用線程時(shí)使用偏向鎖,當(dāng)存在多個(gè)線程使用時(shí)膨脹為輕量級(jí)鎖,而出現(xiàn)比較多的線程競(jìng)爭(zhēng)時(shí)再膨脹為重量級(jí)鎖。
并發(fā)的數(shù)據(jù)結(jié)構(gòu)線程安全的容器,如Vector 、 ConcurrentHashMap等。
讀寫鎖,即java中的ReentrantReadWriteLock。
讀寫鎖又可以看做一個(gè)讀鎖和一個(gè)寫鎖組成的鎖系統(tǒng),讀鎖和寫鎖又叫共享鎖和排它鎖。
BlockedQueue,阻塞隊(duì)列。
Atomic。 java提供的atomic包封裝了一組常用原子類,使用無(wú)鎖方式實(shí)現(xiàn)。
ThreadLocal。每個(gè)線程都擁有一份對(duì)象拷貝,互相不干擾。
其它:
雙重檢查鎖
實(shí)際上是一種對(duì)于線程安全的懶漢單例模式的一種優(yōu)化。
鎖的屬性為了表達(dá)某種鎖的特點(diǎn),也會(huì)有著很多的概念。
但是這種概念對(duì)應(yīng)的不是某一種鎖,而是一類擁有特定屬性的鎖。如:
悲觀鎖和樂觀鎖。
悲觀鎖假設(shè)發(fā)生沖突,并使用各種方式保證一次只有一個(gè)線程使用臨界區(qū)。
樂觀鎖放任線程去使用資源,在執(zhí)行完后判斷剛才是否有其它線程用過(破壞了數(shù)據(jù)完整性),如果是則撤回重試。
公平鎖和非公平鎖。
公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的先后順序來(lái)一次獲得鎖。java中的ReentrantLock是公平鎖。
遞歸鎖(可重入鎖)/非遞歸鎖(不可重入鎖)
同一個(gè)線程可以多次獲取同一個(gè)遞歸鎖,不會(huì)產(chǎn)生死鎖。
如果一個(gè)線程多次獲取同一個(gè)非遞歸鎖,則會(huì)產(chǎn)生死鎖。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/71470.html
摘要:此對(duì)象在線程受阻塞時(shí)被記錄,以允許監(jiān)視工具和診斷工具確定線程受阻塞的原因。阻塞當(dāng)前線程,最長(zhǎng)不超過納秒,返回條件在的基礎(chǔ)上增加了超時(shí)返回。喚醒線程喚醒處于阻塞狀態(tài)的線程。 LockSupport 用法簡(jiǎn)介 LockSupport 和 CAS 是Java并發(fā)包中很多并發(fā)工具控制機(jī)制的基礎(chǔ),它們底層其實(shí)都是依賴Unsafe實(shí)現(xiàn)。 LockSupport是用來(lái)創(chuàng)建鎖和其他同步類的基本線程阻塞...
摘要:耐心看完的你或多或少會(huì)有收獲并發(fā)的核心就是包,而的核心是抽象隊(duì)列同步器,簡(jiǎn)稱,一些鎖啊信號(hào)量啊循環(huán)屏障啊都是基于。 耐心看完的你或多或少會(huì)有收獲! Java并發(fā)的核心就是 java.util.concurrent 包,而 j.u.c 的核心是AbstractQueuedSynchronizer抽象隊(duì)列同步器,簡(jiǎn)稱 AQS,一些鎖??!信號(hào)量??!循環(huán)屏障?。《际腔贏QS。而 AQS 又是...
摘要:線程通過的方法獲得鎖,用方法釋放鎖。和關(guān)鍵字的區(qū)別在等待鎖時(shí)可以使用方法選擇中斷,改為處理其他事情,而關(guān)鍵字,線程需要一直等待下去。擁有方便的方法用于獲取正在等待鎖的線程。 ReentrantLock是Java并發(fā)包中一個(gè)非常有用的組件,一些并發(fā)集合類也是用ReentrantLock實(shí)現(xiàn),包括ConcurrentHashMap。ReentrantLock具有三個(gè)特性:等待可中斷、可實(shí)現(xiàn)...
摘要:物理計(jì)算機(jī)并發(fā)問題在介紹內(nèi)存模型之前,先簡(jiǎn)單了解下物理計(jì)算機(jī)中的并發(fā)問題。基于高速緩存的存儲(chǔ)交互引入一個(gè)新的問題緩存一致性。寫入作用于主內(nèi)存變量,把操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。 物理計(jì)算機(jī)并發(fā)問題 在介紹Java內(nèi)存模型之前,先簡(jiǎn)單了解下物理計(jì)算機(jī)中的并發(fā)問題。由于處理器的與存儲(chǔ)設(shè)置的運(yùn)算速度有幾個(gè)數(shù)量級(jí)的差距,所以現(xiàn)代計(jì)算機(jī)加入一層讀寫速度盡可能接近處理器的高速緩...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
閱讀 3212·2021-11-22 12:01
閱讀 3832·2021-08-30 09:46
閱讀 833·2019-08-30 13:48
閱讀 3274·2019-08-29 16:43
閱讀 1732·2019-08-29 16:33
閱讀 1914·2019-08-29 13:44
閱讀 1477·2019-08-26 13:45
閱讀 2287·2019-08-26 11:44