程序員:多併發基礎的線程【詳細版】

本博客 貓叔的博客,轉載請申明出處

閱讀本文約 “15分鐘”

適讀人羣:Java 初級

學習筆記

基礎概念

線程是無處不在的

先說說幾個基本的概念吧

一個進程中可以包含多個線程,同一個進程中的線程共享該進程所申請到的資源,如內存空間和文件句柄等

從JVM的角度來看,線程是進程中的一個組件(Component)

Java程序中任何一段代碼總是執行在某個確定的線程中

Java中線程分爲守護線程(Daemon Thread)和用戶線程(User Thread)

用戶線程:JVM正常停止前應用程序中的所有用戶線程必須先停止完畢,否則JVM無法停止

守護線程:不會影響JVM的正常停止,通常執行一些重要性不高的任務,如監視其他線程的運行情況

在多線程的運行中,我們需要注意每個段代碼是由哪一個線程去負責執行的,這關係到性能問題、線程安全

System.out.println("The ** method was executed by thread: " + Thread.currentThread().getName());

如上可以看看對應方法是哪個線程負責執行的,當然你可以創新一個新的線程,並由新的線程負責,來驗證你的猜想

Image

創建並運行

在Java中,一個線程就是一個java.lang.Thread的實例

創建一個Thread類,JVM會爲這個線程實例分配兩個調用棧(Call Stack)所需的內容空間

兩個調用棧,一個用於跟蹤Java代碼間的調用關係,另一個用於跟蹤Java代碼對本地代碼(即Native代碼)的調用關係

假設我們在main方法中創建了一個新的線程,那麼新的thread就是main線程的子線程,它們也就是父子線程關係

在默認情況下父線程爲守護線程,則子線程也同樣爲守護,用戶線程也是如此,當然你也可以通過setDaemon方法來修改這一屬性

狀態與上下文切換

人這一生有很多種狀態,線程也是一樣的

我們可以通過getState方法獲取,返回值是Enum(枚舉)

狀態 備註
NEW 有且僅有一次處於此狀態,剛創建而未啓動的線程
RUNNABLE 複合狀態,包括READY和RUNNING,當READY被JVM線程調度器調度則進入RUNNING狀態,RUNNING表示線程正在執行,即run方法代碼正在由cpu執行,當實例yield方法被調用或者線程調度器原因,RUNNING也會轉爲READY
BLOCKED 線程發起I/O操作後或試圖去獲取其他線程的持有鎖,則進入這個狀態,這個狀態不會佔用CPU資源,以上操作後轉爲RUNNABLE狀態
WAITING 執行某些方法(Object.wait()、Thread.join()、LockSupport.park())處於無限等待其他線程執行特定操作的狀態,某些方法(Object.notify()、Object.notifyAll()、LockSupport.unpark(thread)讓線程從WAITING轉換爲RUNNABLE
TIMED_WAITING 處於有時間限制的等待其他線程執行特定操作,如果其他線程沒有執行,時間到了,就自動轉爲RUNNABLE
TERMINATED 執行結束的線程,也是僅有一次的狀態,無論成功或異常

而一個線程從RUNNABLE轉換爲BLOCKED、WAITING、TIMED_WAITING幾個狀態的過程就意味着上下文切換(Context Switch)

上下文信息:包括CPU的寄存器和程序計數器在某一時間點的內容等

就好像你在和父母打電話的時候,突然你女朋友打了進來,你果斷接了女朋友的約會邀請,然後又重新接上父母這邊的並續上剛剛的聊天內容

從RUNNABLE轉到其他,上下文信息(聊天內容)需要先存儲起來,然後再重新進入RUNABLE狀態時,回覆之前保存的線程的上下文信息(聊天內容),在保存和回覆的過程就是上下文切換

在保存或恢復就是需要開銷的,如CPU時間開銷和CPU緩存內容失效等

你可以看看自己的Java程序運行時的上下文切換情況,我這邊是win7,所以用了windows自帶的perfmon

Image

image

Linux也可以用perf命令查看

perf stat -e cpu-clock,task-clock,cs,cache-references,cache-misses java 你的程序名

線程監控

咱們需要把未知的東西變爲已知的,黑盒變白盒

你可以嘗試用JDK自身的jvisualvm監視

image

或是jmc也行

image

優劣

這個其實大家都基本瞭解,所以我不打算細講來着

優勢 劣勢
提供系統的吞吐量 線程安全問題
提高響應性 線程的生命特徵問題
充分利用多核CPU 上下文切換
最小化系統資源使用 可靠性
簡化程序的結構 /

關於生命特徵,可能就會涉及多種鎖

死鎖(Dead Lock):即都擁有自己的鎖但是又在等對方的鎖

T1(L1) 等 L2
T2(L2) 等 L1

活鎖(Live Lock):一個線程一直嘗試某個操作但是無果(就像部分暗戀、舔狗類似····,這個比喻是我和愛人說明後她給我的第一印象)

線程飢餓(Starvation):永遠無法獲得CPU執行機會,永遠處於RUNNABLE狀態的READY子狀態。

相關術語

術語 說明
任務(task) 任務是線程需要做的,不是一一對應,是一個概念,文件是任務,文件裏的多個數據也可以是任務
併發(Concurrent) 多個任務在同一時間段內執行,不是順序執行,是交替執行
並行(Parallel) 多個任務在同一時刻執行
客戶端線程(Client Thread) 有個Hello類,它有自己的方法say(),那麼一個main函數,創建它的實體類並調用say方法,那麼對於Hello類而言,客戶端線程就是main線程
工作者線程(Worker Thread) 有個Hello類,它有自己的方法say(),它有自己的一個WorkThread線程,在初始化時,線程就開始start,類似執行自己的日誌差不多,那麼WorkThread線程就是工作者線程
上下文切換 去看上面的文章內容 :)
顯示鎖 Java代碼可以控制的鎖,包括synchronize和java.util.concurrent.locks.Lock接口的所有類
線程安全 一段操縱共享數據的代碼能夠保證在同一時間內被多個線程執行而仍然保持其正確性

下次再說說,附錄的synchronize和volatile。

我是MySelf,還在堅持學習技術與產品經理相關的知識,希望本文能給你帶來新的知識點。

公衆號:Java貓說

學習交流羣:728698035

現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不定期乾貨。

Image Text

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