[短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全

MDove發表於2018-09-17

前言

最近在總結《Java多執行緒程式設計核心技術》這本書。實話實說:多執行緒程式設計核心技術,這些字眼屬實有些誇大。

但是也不能因為此,就直接否認了書籍本身的價值。這是一篇比較適合入門閱讀的書籍。和我最近在嘗試寫的文章有相似之處,也就是盡力讓知識點,少思考性而多閱讀性。

因此接下來會將這本書的內容,揉到我的接下來的文章中,旨在:真正能在碎片化時代下,進行有效學習。

愛因斯坦:“如果你不能簡單地解釋一樣東西,說明你沒真正理解它。”

[短文速讀-1] a=a+b和a+=b的區別

[短文速讀-2] 過載/重寫,動/靜態分派?(重新修訂)

[短文速讀-3] 內部匿名類使用外部變數為什麼要加final

[短文速度-4] new子類是否會例項化父類

[短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全

正文

毫無疑問,一切的開始。肯定是先介紹介紹概念性的東西。主題是執行緒,但是談到執行緒,勢必不能忘了程式。因此讓我們先來聊一聊執行緒和程式的概念。

接下來讓我們有請文章一貫的主角:小AMDove出場~

程式和執行緒

小A:MDove,我最近在思考一個問題,多執行緒多執行緒。那到底什麼是多執行緒呢?

MDove:想要理解多執行緒,其實就要不得不提一提:程式,以及程式和執行緒之間的關係。

一個比較通俗且常見的解釋:程式:作業系統分配資源的基本單位;執行緒:作業系統排程最小單位。

稍稍學院派的解釋:

來自《現在作業系統(第4版)》 + 維基百科 + 百度百科

程式:

  • 程式的本質是正在執行的一個程式,程式基本上上容納執行一個程式所需要的所有資訊的容器。
  • 在當代多數作業系統中,程式本身不是基本執行單位,而是執行緒的容器。程式本身只是指令、資料及其組織形式的描述,程式才是程式的真正執行例項。
  • 早期面向程式設計的計算機結構中,程式是系統進行資源分配和排程的基本單位。

小A:哦?既然程式是程式的實體,那麼只要程式不就行了?

MDove:這當然是沒有問題啦。但是,一個致命的問題,大家都追求快,更快,非常快!

小A:不不不不不,我就追求慢,堅挺~

MDove:堅挺是吧,你這麼秀,你怎麼不去Tokyo Hot?擱這給我扯犢子。學不學了?不學你去找加藤鷹去。

小A:學學學,我的理解:是不是執行緒比程式佔用資源更少?

MDove:其實關於消耗資源這個問題很難回答。比如Linux、Windows對程式和執行緒的設計就是不同的。如果從Linux的角度出發,程式和執行緒無論是建立還是上下文切換其實不在極端情況下,所謂的效能還是資源,差距並不是很大。差距比較大的一點是:程式是記憶體獨立,而執行緒記憶體共享。

MDove:當然,二者都可以悄摸得去做一些事情,但不同點在於:程式間通訊,遠比執行緒間通訊複雜的。因此在上層高階語言的設計上,執行緒便成了不錯的用於後臺完成耗時操作的一個工具。但是不可否認的一點,多程式同樣扮演者舉足輕重的角色!打個比方,單程式的程式,如果殺死這個程式那麼這個程式所依附的所有執行緒全部死亡。而多程式則不怕,畢竟彼此是獨立執行的程式,因此多程式在拉活方面有著不俗的戰鬥力。

小A:哦~原來如此,那可以聊一聊執行緒麼?

MDove:好的,接下來讓我們看一看執行緒。


執行緒:

  • 是作業系統能夠進行運算排程的最小單位。它被包含在程式之中,是程式中的實際運作單位。一條執行緒指的是程式中一個單一順序的控制流,一個程式中可以併發多個執行緒,每條執行緒並行執行不同的任務。
  • 執行緒可以為作業系統核心排程的核心執行緒;由使用者程式自行排程的使用者執行緒

MDove:舉個小例子:開啟我們計算機上的工作管理員時,程式Tab頁上,我們看到的就是程式;而獨立程式程式的子任務就是執行緒(不絕對,也可以存在多程式的程式)。比如:QQ執行時(程式),就有很多子任務(執行緒)在同時執行:你即能一遍和基友視訊,一遍還能和其他基友文字聊天這就體現了多執行緒,其中每一項子任務都可以理解為執行緒

MDove:對於我們開發者來說執行緒是一個很常見的概念。對於Java後臺來說,能夠熟練的掌握對多執行緒的使用。可以說是掌握核心科技一樣。

小A:???聽你這麼說,多執行緒沒什麼難的呀?

多執行緒的弊端

MDove:初生牛犢不怕虎,但你要明白,虎終究是虎。多執行緒的一大難點在於執行緒安全問題。因為記憶體共享的原因,導致了執行緒重新整理記憶體的滯後性。

小A:???什麼意思???

MDove:打個比方,在Java執行緒模型中:每個執行緒執行時,都會把主記憶體中的變數值複製到自己的工作記憶體當中,當自己執行完畢後,在把計算完畢的工作記憶體中的變數,反過來賦值給主記憶體。

小A:嗯?這好像沒什麼問題啊?

MDove:沒問題?問題大了去了!我們們舉一個花錢的例子:現在我們們賬本上有十個億。你是一個執行緒,我是一個執行緒。

  • 我先執行了,我拿了一個億,去建設了一下社會主義核心價值觀,然後我在我的賬本里記了一下賬:我拿了1個億,手頭還有9個億

此時我們們的賬本還剩9個億

  • 接下來,你也出動了,你拿了9個億中的一個億,你也在你的賬本里記了一下賬:我拿了1個億,還有8個億。然後你去吸菸喝酒燙頭了。

此時我們們的賬本還剩8個億

  • 這個時候我幹完了我的事,我花了5千萬,還剩5千萬。然後對於我來說我的賬本變成了,還有9億5千萬。此時我把我的賬本寫到了我們們們公共賬本里...

小A:等會等會?你想啥呢?錢越花越多了!我都拿走1個億了,你那咋還9個億呢?你就不會同步一個我的賬本麼?

MDove:沒錯呀,我就不會同步一個你的賬本呀!這就是多執行緒的問題所以。因為每個執行緒是獨立執行的,誰都不管誰,因此,如何同步執行緒之間的資料便成了至關重要的一點!

小A:這麼一說我就明白了,那怎麼同步呢?

保證執行緒安全

可見性,原子性

MDove:其實剛才的問題就出現在賬本的不同步上,因此如果我們能夠解決賬本的同步問題,理論上就可以解決我們們的執行緒安全問題。當然你可以用一些手段通知我,讓我更新我的賬本(可見性)。但是仍存在問題,如果我們寫賬本的操作是個多步驟的複雜操作,可能就會存在問題了,因為這個裡每一步通存在同步問題,只有當我們的寫賬本操作是一個單一操作(原子性)那麼這種做法就是沒有問題的。

小A:侷限還挺多,那有沒有其他方式呢?

加鎖

MDove:接下來說一個加鎖的方式。舉個我們們上廁所的例子。我們很多人都要上廁所,如果我們的廁所沒有任何措施,那麼畫面簡直無法想象。因此,我們們...那啥...大號...的時候,都會把門鎖上。其他人一看門鎖上了,也就只好默默的憋一會。

MDove:當我們解決後,開啟門,其他人就可以盡情釋放了。

那麼這個過程就相當於:一個執行緒在操作一個變數時,直接把這個變數鎖起來。其他執行緒只能等著,因此肯定就不會存在多個執行緒同時操作變數的問題了。

執行緒優先順序

小A:那麼接下來就是隊伍中第一個人去...那啥麼?

MDove:當然不是,執行緒的世界可沒有什麼先來後到。而是按執行緒的優先順序去排列。那麼如果沒有優先順序,那就看誰的拳頭更硬誰的運氣更好了。

小A:好殘暴,那有沒有順利排列的可能性呢?那我們可不可以自己固定一個順序去開啟執行緒的執行呢?

執行緒執行順序

MDove:我們當然可以指定一種策略去順序的start我們的執行緒,但是我們只能保證執行緒start的順序,沒辦法保證執行緒排程的順序。因為對於執行緒來說,什麼時候能夠獲得作業系統的寵幸那是不確定的。因此執行緒會有多種狀態:

  • 新建(NEW),表示執行緒被建立出來還沒真正啟動的狀態。

  • 就緒(RUNNABLE),表示該執行緒已經在 JVM 中執行,當然由於執行需要計算資源,它可能是正在執行,也可能還在等待系統分配給它 CPU 片段,在就緒佇列裡面排隊。

  • 阻塞(BLOCKED),阻塞表示執行緒在等待鎖釋放。比如,執行緒試圖去獲取某個鎖,但是其他執行緒已經獨佔了,那麼當前執行緒就會處於阻塞狀態。

  • 等待(WAITING),表示正在等待其他執行緒採取某些操作。。

  • 終止(TERMINATED),不管是意外退出還是正常執行結束,執行緒已經完成使命,終止執行。

當然也可以加上一個:執行(RUNNING):可執行狀態(RUNNABLE)的執行緒獲得了CPU時間片,開始執行程式碼。

分段鎖

MDove:我們們再回到那個廁所的例子。我們日常中...廁所肯定不止一個坑位,一般會有好幾個。畢竟都是解決同樣的問題,沒有隻設定一個坑位的道理。

MDove:所以對於我們程式來說也是如此,在面對型別的場景也會有類似的實現,這個方式就叫做分段鎖。比如:ConcurrentHashMap,JDK1.8版本之前的設計。

MDove:當然關於鎖這個話題,其實水是很深的。我們嘚吧嘚說了這麼多,其實就是在解決多執行緒安全問題。因此明白了吧,多執行緒是一個值得深入學習的內容。

小A:學的我熱血沸騰的,接下來教教我,Java中對執行緒的使用吧!

MDove:別急,聊了這麼久廁所的話題,容我上個廁所,我們們下期再來聊一聊執行緒的使用。

劇終

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,以及我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:IT面試填坑小分隊

相關文章