多線程編程引子:進程、線程、線程同步

多線程編程引子:進程、線程、線程同步

前言

最近在總結《Java多線程編程核心技術》這本書。實話實說:多線程編程核心技術,這些字眼屬實有些誇大。

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

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

正文

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

接下來讓我們有請文章一貫的主角:小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:別急,聊了這麼久廁所的話題,容我上個廁所,咱們下期再來聊一聊線程的使用。

劇終


分享到:


相關文章: