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