操作系統的發展使得多個程序能夠同時運行, 由操作系統來分配資源, 如果需要的話, 進程會通過一些原始的機制相互通信, 主要分為消息傳遞和共享內存兩種:
消息傳遞: Socket, 信號處理(signal handlers), 信號量(semaphores)和文件等.共享內存
線程共享其所屬進程的內存地址空間, 因此所有同一進程中的線程訪問相同的變量, 并從同一個堆中分配對象, 這相對于進程間通信(inter-PRocess)機制來說實現了良好的數據共享. 但是如果沒有明確的同步來管理共享數據, 一個線程可能會修改其他線程正在使用的數據, 產生意外的結果.
java線程中, 通過一個int
變量priority來控制優先級, 范圍為1~10, 通過setPriority(int)
方法來修改優先級, 默認優先級是5, 操作系統通常會忽略自己手動設定的線程優先級(比如Mac OS X, Ubuntu等), 作為程序開發者一般也不需要手動調整
一共有6種可能的狀態:
狀態名稱 | 說明 |
---|---|
NEW | 初始狀態, 僅被構建, 沒有啟用start() 方法 |
RUNNABLE | 可運行狀態, 將操作系統中定義的’就緒’和’運行’統稱為’RUNNABLE’ |
BLOCKED | 阻塞狀態, 需要獲取鎖 |
WAITING | 等待狀態, 需要其他線程發出通知或者中斷, 然后重新競爭鎖 |
TIMED_WAITING | 定時等待狀態, 和WAITING類似, 不過不會無限等待,具有一個超時自動返回的功能, 不會引起鎖持有狀況的變化 |
TERMINATED | 終止狀態, 線程已執行完畢 |
可以使用jstack工具來進行查看運行時線程的信息, 步驟如下:
使用jps
命令查看Java進程;找出當前運行程序的進程號, 這里以2100為例, 運行jstack 2100
即可.Java線程狀態變遷示意圖:
Daemon線程是一種后臺支持型線程, 因為它主要被用作程序中后臺調度以及支持性工作. 當一個JVM中不存在非Daemon線程的時候, JVM將會立即退出(所以不能在finally
塊中執行關閉或者清理資源的邏輯)
實際上是通過共享內存實現的線程間通信.
是任何Java對象都具備的, 這些方法定義在java.lang.Object
上:
方法名稱 | 描述 |
---|---|
notify() | 通知一個在對象上等待的線程, 使其從wait() 方法返回, 而返回的前提是該線程獲取到了對象的鎖 |
notifyAll() | 通知所有等待在該對象上的線程 |
wait() | 調用該方法的線程進入WAITING狀態, 同時釋放對象鎖, 只有等待另外線程的通知或被中斷才會返回 |
wait(long) | 定時等待, 單位毫秒, 超時返回 |
wait(long, int) | 更細粒度的定時, 可以達到納秒級別 |
就是消費者/生產者模型
實際上是通知/等待機制的一種實現.
是線程變量, 以ThreadLocal
對象為鍵, 任意對象為值的存儲結構. 這個結構是與線程對象綁定的, 也就是說即使是使用線程不安全的數據結構, 如此包裝后也可以安全使用.
并發編程中有許多單線程程序中沒有的挑戰.
即使是單核CPU也支持多線程執行代碼, CPU會通過給每個線程分配CPU時間片來實現這個機制. 時間片一般是幾十毫秒. 當前任務執行一個時間片后會切換到下一個任務, 但是在切換前會保存上一個任務的狀態, 以便下次切換回這個任務時, 可以再加載這個任務的狀態. 所以任務從保存到再加載的過程就是一次上下文切換. 上下文切換會影響多線程的執行速度.
并發執行的速度不一定比串行快, 因為線程有創建和上下文切換的開銷. 當并發運算次數較少的時候, 上下文切換的開銷會顯著影響累積計算時間.
無鎖并發編程
多線程競爭鎖時, 會引起上下文切換, 避免使用鎖的方法舉例: 通過將數據的ID按照Hash算法取模分段, 不同的線程處理不同段的數據.
CAS算法
Java的Atomic
包使用的就是CAS算法來更新數據, 不需要加鎖, 舉例: AutomicLong
類型
使用最少線程
避免大量線程處于等待狀態
協程
在單線程里實現多任務的調度, 在單線程里維持多個任務間的切換.
避免死鎖的幾個方法
避免一個線程同時獲取多個鎖;嘗試使用定時鎖, 使用lock.tryLock(timeout)
來替代使用內部鎖機制;無狀態對象永遠是線程安全的
這里以一個Servlet為例, 說明什么叫做無狀態:
因為Servlet對象不包含屬性, 也沒有引用其他類的屬性, 只有本地變量. 本地變量唯一的存儲在本線程的棧中, 只有執行線程(本Servlet)才能訪問, 所以是線程安全的.
count++
check-then-run, 不是原子的, 例如: if (i != null) i = new SomeClass();
無狀態+單個內置原子變量=線程安全, 但是兩個及以上的原子變量!=線程安全
為了維持狀態一致性, 必須將相關的狀態變量在同一個原子操作內更新
注意
并不是說將一個類的所有方法都標注成synchronized
, 然后這個類就是線程安全的了, 舉個例子, 如果兩個屬性之間需要同步更新, 然后分別提供了setter
方法, 那么在類外使用的時候, 就有可能引發安全問題.
新聞熱點
疑難解答