为什么多个线程不可能同时抢到一把锁_并发基础理论:原子性问题、锁、管程...
我們?cè)倩仡櫼幌?#xff0c;原子性問題的根源是CPU切換線程執(zhí)行指令所導(dǎo)致的,當(dāng)前一個(gè)對(duì)共享變量的操作沒有完成之前,CPU又切換到另外一個(gè)線程來操作對(duì)應(yīng)的共享變量,那么最終產(chǎn)生的結(jié)果就可能出現(xiàn)問題。
比如如果現(xiàn)在有兩個(gè)線程都在執(zhí)行number=number+1,他們最終的結(jié)果可能還是為1,因?yàn)镻U執(zhí)行流程可能會(huì)如下,:
如解決原子性問題
從上面的案例看,原子性問題的丟失完全是因?yàn)镃PU切換線程執(zhí)行指令導(dǎo)致的,那么是否意味著只要禁止CPU切換線程執(zhí)行指令就可以呢,結(jié)果是行不通的,禁止CPU切換指令在單核CPU的確可以解決這個(gè)問題,但是多核CPU的場(chǎng)景下,CPU可以同時(shí)調(diào)度多個(gè)線程執(zhí)行指令,那么該問題還是存在的。
所以我們必須另找出路,回過頭來思考,我們會(huì)發(fā)現(xiàn)一個(gè)共性,就是不管是線程切換還是多核CPU同時(shí)執(zhí)行指令,其實(shí)根本原因就是,對(duì)于共享變量在修改操作,在一個(gè)線程沒有完成之前,另外一個(gè)線程是可以同時(shí)介入操作,所以才會(huì)導(dǎo)致一個(gè)線程的結(jié)果可能被另外一個(gè)線程覆蓋。如果從這個(gè)角度來考慮的話,那么是不是只要達(dá)成一個(gè)線程在操作共享變量的過程中,另外一個(gè)線程是不能介入操作,只有等前面一個(gè)線程執(zhí)行完之后,后面的線程才可以操作,也就是讓兩個(gè)線程對(duì)于共享變量的操作是互斥的,那么問題就可以解決,而讓兩個(gè)線程操作互斥我們常用的手段就是“加鎖”。
互斥鎖
能保證多個(gè)線程(進(jìn)程、操作者)對(duì)于共享變量(共享資源)的操作是互斥的也就是我們常說的“互斥鎖”,鎖是一個(gè)通用的概念在很多領(lǐng)域都有鎖的機(jī)制、使用鎖的目的也很簡(jiǎn)單,就是“保證操作的原子性”。
鎖這個(gè)名字雖然很形象,但是類比到我們現(xiàn)實(shí)世界往往容易造成困惑,比如現(xiàn)實(shí)世界的門鎖,我們開門的必須是用鑰匙,而不是需要獲取鎖,而且現(xiàn)實(shí)世界一個(gè)鎖會(huì)有多個(gè)鑰匙,這在編程領(lǐng)域是不允許的,所以我更愿意把鎖的意思解釋成“使用權(quán)”。每個(gè)操作者需要操作共享資源時(shí),必須首先獲得這個(gè)共享資源的使用權(quán)才可以進(jìn)行操作,而當(dāng)一個(gè)人擁有了共享資源的使用權(quán)之后,另外一個(gè)人是想要操作共享資源就之后就只能等待前者操作結(jié)束后釋放共享資源的使用權(quán)。
當(dāng)我們對(duì)某個(gè)共享資源加鎖之后,如果線程想要訪問共享資源,那么它首先要拿到這個(gè)對(duì)象的鎖,當(dāng)某一個(gè)線程獲取到鎖時(shí),它便可以訪問共享資源, 沒有獲取到鎖的線程只能等待,直到上一個(gè)線程執(zhí)行完畢之后釋放鎖再進(jìn)行下輪鎖的競(jìng)爭(zhēng),因?yàn)橹挥幸话焰i,所以永遠(yuǎn)只會(huì)有一個(gè)線程操作該資源。加了鎖之后那么最后執(zhí)行的流程就如下:
管程模型
使用互斥鎖是為了線程杜宇共享資源的互斥性,對(duì)于共享資源的操作只允許有一個(gè)線程進(jìn)行。但是在鎖的獲得與釋放線程之間需要如何進(jìn)行配合和協(xié)調(diào)又是一個(gè)問題,這也就是線程“同步”問題,所以解決共享變量的訪問過程的原子性其實(shí)需要解決兩個(gè)問題,一個(gè)是線程之間的互斥,二是線程之間的協(xié)調(diào)同步。對(duì)于這兩個(gè)問題計(jì)算機(jī)領(lǐng)域有有一種成熟的方法論來解決,它就是管程。
管程是一個(gè)抽象的概念模型,為了解決多個(gè)進(jìn)程或線程同時(shí)訪問一個(gè)共享資源時(shí)能達(dá)到"互斥"和"同步"的效果,,它定義了管理共享資源的訪問過程的模型,任何語言都可用通過都可以通過這套模型編寫出安全的并發(fā)程序,管程實(shí)現(xiàn)必須達(dá)到下面幾點(diǎn)要求
1、管程中的共享變量對(duì)于外部都是不可見的,只能通過管程才能訪問對(duì)應(yīng)的共享資源(意思是共享變量的操作必須通過管程,無法通過其他途徑操作)。
2、管程是互斥的,某個(gè)時(shí)刻只能允許一個(gè)進(jìn)程或線程訪問共享資源(線程對(duì)于管程的訪問是互斥的)。
3、管程中需要有線程等待隊(duì)列和相應(yīng)等待和喚醒操作(沒獲得鎖的線程放入一個(gè)隊(duì)列中等待,等前一個(gè)線程釋放鎖后可以通過某種機(jī)制喚醒等待隊(duì)列中的線程)。
4、必須有一種辦法使進(jìn)程無法繼續(xù)運(yùn)行時(shí)被阻塞(在程序要求的邏輯條件不滿足的時(shí)候,可以使其阻塞)。
我們來理解下上面幾個(gè)條件:
首先第1點(diǎn) 和第2點(diǎn)我們都能理解,只能通過管程訪問共享資源,并且每次只能有一個(gè)線程獲得管程的執(zhí)行權(quán),這兩個(gè)要求理解起來很簡(jiǎn)單,其實(shí)就是為了讓線程之間達(dá)到互斥的效果。
然后看第3點(diǎn)要求,管程中要有等待隊(duì)列和響應(yīng)的等待和喚醒操作,這個(gè)也好理解,等待隊(duì)列和喚醒可以使線程之間達(dá)到同步有序的執(zhí)行。
第4點(diǎn)是比較讓人費(fèi)解的,什么時(shí)候線程會(huì)無法繼續(xù)運(yùn)行呢?為什么要在這個(gè)時(shí)候提供線程可以進(jìn)入阻塞的方法。
咱們看一個(gè)案例:
場(chǎng)景:假如我們正在開發(fā)一個(gè)互聯(lián)網(wǎng)項(xiàng)目;
角色:項(xiàng)目參與人員有產(chǎn)品經(jīng)理、開發(fā)人員、測(cè)試人員參與;
限制:只有一個(gè)辦公室可以使用,一個(gè)辦公室一次只能容納一個(gè)角色進(jìn)入。
節(jié)點(diǎn): 每個(gè)角色負(fù)責(zé)對(duì)應(yīng)的節(jié)點(diǎn),產(chǎn)品經(jīng)理產(chǎn)品文檔、開發(fā)人員產(chǎn)出項(xiàng)目代碼、測(cè)試人員測(cè)試代碼質(zhì)量、產(chǎn)品進(jìn)行驗(yàn)收。
條件:開發(fā)人員必須有了產(chǎn)品文檔之后再產(chǎn)出項(xiàng)目代碼、測(cè)試人員在開發(fā)人員開發(fā)完畢了之后進(jìn)入測(cè)試、產(chǎn)品人員在測(cè)試完畢了之后進(jìn)行驗(yàn)收。
在這個(gè)場(chǎng)景里面,多個(gè)角色就是系統(tǒng)的多個(gè)線程,辦公室是一個(gè)共享資源同一時(shí)刻只能有一個(gè)角色進(jìn)入,這個(gè)場(chǎng)景里面就有一個(gè)阻塞場(chǎng)景,就是當(dāng)一個(gè)開發(fā)人員搶到了辦公室鑰匙之后,進(jìn)入到辦公室,結(jié)果發(fā)現(xiàn)產(chǎn)品的需求都沒有出來,這個(gè)時(shí)候開發(fā)人員是沒有辦法進(jìn)行工作的,所以只能一直等,等到有產(chǎn)品文檔之后繼續(xù)下一步,但是這個(gè)時(shí)候產(chǎn)品是沒辦法進(jìn)入辦公室工作的,因?yàn)殒i在開發(fā)人員手里,所以開發(fā)人員一直等不到需求文檔,而產(chǎn)品經(jīng)理一直進(jìn)入不了辦公室,導(dǎo)致死鎖。
那么這里就需要有一種方式,當(dāng)開發(fā)人員發(fā)現(xiàn)條件不成立的時(shí)候,此時(shí)開發(fā)人員可以主動(dòng)的放棄辦公室的鎖,然后告訴辦公室門口的產(chǎn)品經(jīng)理,讓產(chǎn)品經(jīng)理先進(jìn)辦公室完成工作,開發(fā)人員自己則進(jìn)入一個(gè)等待隊(duì)列,當(dāng)產(chǎn)品經(jīng)理完成了工作之后,產(chǎn)品經(jīng)理通知開發(fā)人員,然后自己放棄房間鑰匙,等待需求驗(yàn)收再開始下一輪的工作。
最后以這種條件阻塞的方式讓獲得鎖的線程可以主動(dòng)讓出鎖,并等待其他線程喚醒再來檢測(cè)條件,避免了某一個(gè)線程因?yàn)闂l件不滿足導(dǎo)致任務(wù)無法進(jìn)行,而因?yàn)閯e的線程無法進(jìn)入到管程里,導(dǎo)致這個(gè)條件永遠(yuǎn)也無法改變鎖造成的死鎖問題。
下面這張圖雖然不嚴(yán)謹(jǐn),但是有助于你理解整個(gè)管程模型:
JAVA中的管程
通過上面的管程我們?cè)賮砜碕AVA里面的管程,JAVA是通過Synchronized關(guān)鍵字,和wait()、notify、notifyAll() 方法實(shí)現(xiàn)了整個(gè)管程模型, 與上面標(biāo)準(zhǔn)的管程模型不同的是,JAVA的Monitor屬于一種簡(jiǎn)單的管程模型,因?yàn)樗]有使用多個(gè)條件變量的隊(duì)列,不管是競(jìng)爭(zhēng)鎖產(chǎn)生的阻塞,還是拿到鎖因?yàn)槟硞€(gè)條件不合格導(dǎo)致的阻塞,統(tǒng)一都放入一個(gè)隊(duì)列了。
下面我們同樣通過一張圖來理解:
總結(jié)
以上是生活随笔為你收集整理的为什么多个线程不可能同时抢到一把锁_并发基础理论:原子性问题、锁、管程...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POSIX多线程API函数
- 下一篇: mugen4g补丁如何使用_CAD如何去