Java多線程基礎知識---併發與並行基礎-1

一、併發與並行基礎

0.抓住那個CPU

  1. 計算機的運算速度非常快,但是在我寫上一句話的時候,我想表達的最深層的意思是:CPU的運行速度很快。早在第一臺計算機(對,就是那個賊大的,一個房間那麼大的計算機)問世的時候,CPU執行運算的速度就直接將我們(也就是在壓榨CPU計算力的外部人員,還有IO設備)遠遠的拋到後面,現代CPU的計算能力早就不是早期CPU所能媲美的,速度比起人類來,快的那叫一個喪心病狂。現在你可以想象以下這個場景:

    你在用計算機計算你的一道線性代數題目,你在鍵盤上輸入你的表達式、限制條件,大概單身20年的手速也要那麼幾秒鐘,如果你電腦的CPU只幹一件事(當然是不可能的):等你輸入表達式完成後計算其結果。然後你驚訝的發現:你冥思苦想算不出來的幾元幾次方程,剛敲確定鍵電腦就給算出來了,當你驚訝於CPU的高效率時,是否想過,在等你輸入的這段時間裏,以CPU那麼優秀的能力,在這段時間CPU是不是很閒?

  2. 在剛纔的場景中,CPU的確很閒。但是在實際的使用場景中,你可能一邊輸入着表達式,一邊聽着網易雲的日推薦,還在後臺下着百度雲資源,再加一個什麼QQ、微信什麼的,但是你發現在你按確定鍵的時候,它還是馬上把它算出來了!並且你的音樂也沒有停,百度雲也還在下載着資源,但是比起上一個場景,就好像CPU有分身術一樣,同時的處理着多個任務,而且這多個任務好像是在同時運行的,並沒有間斷一樣,至少你沒有發現。打開計算機的任務管理器會看見如下的情景:
    計算機的CPU處理任務和部分資源佔有情況
    看見了吧?CPU其實很同時在幹很多事情的!

  3. 剛纔的場景,我並沒有感覺到在我寫博文的時候網易雲的音樂停止,所以在我感覺來看,這些任務是同時在進行的(當然還有另外的極端情況:打開軟件太多了,會感覺明顯的卡頓:任務太多了,CPU都受不了了,同時CPU的佔有率會狂飆)。
    剛纔所瞎扯的,是進程層面上的併發。併發的最根本目的就是爲了更好的壓榨CPU這個超級勞動力,爲我們做更多的事情,讓優秀的CPU同學多發光發熱。

1.硬件工程師的甩鍋

想必你一定聽說過摩爾定律,直白的說就是:每18-24個月,我們的CPU處理能力將翻倍。但是萬事都是有極限的,CPU主頻在接近4GHz的時候就已經很難上升了(因爲芯片組件直徑已經達到納米級別),然後硬件工程師突發奇想的把幾個CPU內核集成,於是就有了多核處理器(主要是服務器的硬件,我的微機是單核的)。現在計算機的內核數量不斷地在上升,在提高計算機計算性能的時候也在考驗着軟件工程師:如何發揮出多核計算機系統的最大計算潛力,答案就是:並行。
然後就出來了這個亂七八糟、苦不堪言的多線程問題。你一定會認同我的這個觀點,如果你學多線程的話。

2.併發和並行的區別

你應該聽說過一個和併發很像的詞語:並行。但是併發和並行兩個之間有什麼聯繫和又有區別呢?

併發:簡單的併發理解就是多個任務在高速的切換運行,讓人彷彿以爲他們在同時運行。
其實不是的,併發的概念主要是針對單核處理機(只有一個物理內核的處理機或者多核處理機的一個具體的物理內核),因爲只有一個CPU,所以多個任務只能間接的佔用CPU執行,每個任務會佔用CPU的一定時間(CPU的進程調度,視具體的調度算法而定,簡單的就是時間片輪轉調度算法),但是這個時間間隔人類幾乎無法察覺(可能是10-100ms,或者更短,也許你會覺得太短了,但是這點時間已經可以讓CPU執行上千條指令),所以就算是任務是間隔運行的,我們會產生我們的任務在同時運行的錯覺(當然,任務太多了,CPU也忙不過來,所以你就可以察覺了,比如你的軟件變得很卡)。
並行:簡單的並行概念也是同時運行多個任務,是真正意義的同時運行。並行的概念是針對多核處理機的,(這裏和上面所提到的內核都是物理內核,而不是超線程技術實現的邏輯核,邏輯核是和併發相識的概念),多核處理機的多個CPU內核可以做到真正的同時進行,有多少個物理內核,就可以有多少個並行的任務,互不干涉。

注:進程的上下文切換和多CPU分別運行一個進程的一部分線程的情況就涉及到操作系統太深了,我就不提了(主要是不太瞭解),同時在下面的博文中,將不再對併發和並行進行區分,統一稱爲:併發。

3.併發的思想基礎

併發編程還需要思想基礎?沒錯!併發編程必須要有併發的編程思想基礎(也就是做好頭疼的準備,併發會挑戰認知極限),併發編程與順序編程的最大不同就是:
我不能控制其執行的行爲,包括他什麼時候開始,什麼時候得到結果。在併發編程中,切記:一切非原子性操作(最基本的計算機指令,要麼都成功,要麼都失敗)都可能在任何步驟被打斷。

舉一個荔枝(interesting):世界被上帝詛咒了(上帝不會詛咒自己),每一次只能有一個人行動,但是讓哪一個人行動是上帝的權利。在這樣的世界裏,張三、李四快樂的生活着,一天他們在吃飯,菜在桌子上的。吃到菜需要伸手、夾菜、放到嘴裏三個步驟。他們感情很好,不會搶別人筷子上的東西。那麼吃到菜的三個動作就是原子性操作,不可打斷。但是注意一點,吃菜需要三步。好!現在他們開始吃飯了。上帝先讓張三可以行動,於是張三伸出了手準備夾那塊最大的紅燒肉,這個時候不知道爲什麼上帝生氣了(就像不知道女朋友爲什麼生氣一樣,可能覺得他太貪心了),上帝剝奪了張三可以活動的權利(手一直伸着),然後讓李四可以行動。李四先伸出手,再夾了一塊青菜,再放到嘴裏,再伸出手,夾了那塊最大的火燒肉,放到了嘴裏。這個時候上帝不生氣了(可能覺得李四也不咋地),重新讓張三可以行動(自然李四就不能行動了),這個時候張三就會一臉蒙逼:我要夾的那塊最大的紅燒肉呢?

說上面的例子是爲了說明一個問題,在計算機的世界,任何進程的非原子性操作都可能被馬上停止(也就是中斷當前任務去忙其他的任務),也就是CPU隨時都可能去忙別的任務,這一點思想在併發編程裏面至關重要。

4.共享資源

共享資源,也叫臨界資源。指的是那種會在多個任務間共享的一些信息、設備(也就是計算機系統裏面的資源),比如一些公共的變量,共享的系統設備:如打印機等,對共享資源的訪問和使用必須要小心翼翼,不然就會出現上一個紅燒肉例子的那種情況,需要的東西拿不到(或者不是你實現想象的該資源應該有的樣子,因爲他的狀態被別人修改了)。再打個比方,如果不對打印機的使用進行控制,同時打印兩份文檔打印出來的東西肯定不是你想要的,所以,對這些危險的東西的訪問必須遵循一些原則,也就是要守規矩。
在多線程中,最麻煩的一個部分就是對臨界資源的安全訪問。

5. 同步和異步,阻塞與非阻塞的概念

同步和異步都是用來形容一次方法的調用。

同步就是:我讓你去給我買只雪糕,在你沒有把雪糕買回來之前,我不會做任何事情,等你把雪糕買回來了,我就吃雪糕。
異步就是:我讓你去給我買只雪糕,給你說了之後我就繼續碼代碼了,等你把雪糕買回來了,我就吃雪糕。

阻塞與非阻塞是形容任務,也就是線程之間的影響程度。

例如:牆上一個表,大家都可以同時看錶上的時間,那麼我們看錶的這個動作就是非阻塞的,因爲我看錶不會影響你看錶。但是比如我們兩個只有一隻眼鏡,在我用這副眼鏡的時候你就不能用,那麼我用眼鏡的這個動作對你用眼鏡的動作就是阻塞的。而上面的眼鏡和表就是臨界資源,臨界資源的訪問控制要視情況而定(是否會改變資源的狀態)。

6.死鎖、活鎖、飢餓的概念

再舉個栗子:我們兩個吃飯,一碗飯,一雙筷子,現在有以下的場景:

場景一:我拿了一隻筷子,你拿了一隻筷子,我想要你的那隻筷子,但是我們都很餓,你肯定不會給,我也肯定不會給你,那麼我們都只有一隻筷子,無法吃飯,然後我兩就餓死了,所以叫死鎖。開個玩笑,實際上是因爲我們都沒有辦法繼續我們吃飯的動作,因爲我們都還差一點資源,也就是另一雙筷子。
場景二:我們很有禮貌,如果我拿不到另外的一隻筷子,就會把我的這隻筷子放下來,然後再重新去拿。這個時候就可能出現我拿一隻,你馬上拿了另一隻,我放下這一隻,你馬上放下另一隻的死循環。然後大家都吃不到飯,這種情況叫活鎖。
場景三:我的速度比較快,我先拿了一隻筷子,再哪裏另外一隻筷子,然後吃飯,放下筷子,再不停的重複,直到吃飽了。你肯定會問,爲什麼你沒有拿到筷子?這個是因爲一些其他的原因,比如線程優先級。高優先級可以有更多的CPU執行機會,所以看起來高優先級的線程執行會更快一下。但是你也是可以先拿到筷子(小概率事件在大量事件中必然發生),那麼我只有把筷子讓給你了哦,但是你吃的少一些,吃完了可能都是飢餓的,所以這種情況叫飢餓。極端的情況會把低優先級的線程任務直接餓死,一直拿不到CPU的執行權。

7.小結

在以上的內容中,簡單提了一下什麼是多任務以及多任務會出現的問題,當然作重要的還是上帝的那個例子,多線程的基本思想,有了這些可以開啓Java多線程之旅了。

[注]:詳細知識請參見:計算機操作系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章