java多線程的雜談

java的多線程

java的多線程的概念,向來都是很複雜、籠統、抽象的。現實世界只有將知識點抽象事後纔能有效的傳播,可是傳播的過程當中,只有將抽象的知識點具象化,咱們才能習得。因此咱們會將個別內容點進行一個具象化進而解剖。當咱們理解完了以後最終將其抽象成一個個名詞:多線程、資源、鎖等。java

本文僅從如下的範圍內容來談談java的多線程。程序員

  1. 何爲線程,線程的做用
  2. 資源的控制,鎖的介紹
  3. 線程池的做用
  4. 多線程的經常使用工具和方法

1.何爲線程

1.1.線程的定義

官方解釋:線程是一個單一的順序控制流程編程

線程分主線程、子線程。由主線程來建立子線程來執行各類任務。多線程

舉例說明:以動漫「火影忍者」舉例說明,主線程就比如每個忍者,他們構成了最基本的忍者世界,一個忍者能夠按照任務的緩急、難易程度同時執行多個任務。同時忍者也能分身(調用自身查克拉)來分擔自身的任務,這就比如,忍者世界觀中的忍者主體,基本等同於程序中的主線程。主線程沒了,分身則主觀上不可控,就消失了。異步

圖片描述

由上圖可見,主線程與子線程的關係和忍者與分身的是很類似,也就是說,主線程能作的事,咱們都能讓子線程幫咱們作。忍者本身能作的事也能去靠分身去作。接下來,咱們來看兩段代碼。工具

//代碼一
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
複製代碼
//代碼二
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println("Hello World!");
        });
        thread.start();
    }
複製代碼

代碼一和代碼二最終的結果都是隻作了一件事,向控制檯輸出「Hello World!」。可是代碼一是由主線程去作的;代碼二是由主線程建立的子線程去作的。這裏咱們能夠看出,主線程和子線程的本質上區分並不大,由於它們都執行相同的邏輯,這一點上並無進行區分。性能

1.2.多線程的做用

程序若是都是按照單個線程的話,那麼全部任務的執行均是按照順序來進行(串行執行)。優化

而多線程的做用是能夠安排不一樣的線程執行不一樣的任務spa

圖片描述

上圖是一個理論值,咱們在某些任務密集的場景下,多線程的執行效率多數狀況下可能高於單線程的執行。線程

爲何說是可能,由於這裏建立子線程是會消耗性能的,也帶有時間消耗,若是設置不合理,單單建立子線程的時間成本就遠大於執行任務的時間成本,這一點要結合實際場景進行考慮。

舉例說明:火影裏的忍者也不會接到一批任務就立刻分身去一個個的作,他們也得結合任務的實際狀況來考慮使用分身。

1.3.關於線程的小結

  1. 線程能夠執行正確的邏輯代碼
  2. 線程的建立也伴隨着性能消耗,並非無消耗

2.資源的控制

java中的資源能夠理解爲,一個實例或基礎數據類型的變量的任何操做。實例或變量在這裏不能徹底算作資源,由於根據面向對象編程中的封裝性,代碼中直接將實例暴露出來給非本類的實例進行操做是一個大忌。

圖片描述

這裏咱們從如下資源和線程的關係進行解剖。

  1. 一個線程能夠執行多個資源(串行)
  2. 多個線程能夠執行多個資源(並行)
  3. 單個資源能夠被一個線程執行(串線)
  4. 單個資源能夠被多個線程執行(並行)

從這裏看,1和3沒什麼問題。由於這種機制下咱們確保了一個資源被一個線程執行(等同於一個任務被一個忍者(本體或分身都可)執行)。可是2和4就出現了一個現象,同一個資源被多個線程所操做,若是不加以控制,則會出現指定以外的執行結果或者直接產生死鎖。

舉例說明:兩個忍者都執行了同一個任務,去殺死鄰國的頭目,咱們假設忍者A過去殺死了頭目,忍者B後去的,發現頭目死了,那他接下來怎麼辦?算任務失敗仍是算完成了?

固然忍者B最後確定仍是回去覆命了,也算他任務成功,這是任務自己的規則和秩序所決定的,可是程序的世界是無秩序,須要程序員經過代碼去打造這個無序的世界從而造成秩序

資源自身必定要包含約束性和規則性才能被正確的使用。

2.1.常見的控制方式

java自己提供了資源被多個線程調度的控制方式。

  • synchronized關鍵字
  • Atomic包
  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • CyclicBarrier
  • Phaser

咱們經過一張圖表來概況瞭解一下。

圖片描述

對於資源的控制的方式無非就是一個「」 字。現實當中處處充斥着這樣的例子,例如一個城市的市長,按照規定只能有一個,誰上任,那麼市長這個資源位就被誰「鎖」住了。可是程序世界中的「鎖」和現實世界中的「鎖」差異很大。

  • 現實世界的鎖,是可見,它控制着某同樣可見的物體,好比:門、箱子等,而再由這些具備隔絕性質的物體去控制級別更高的資源,例如:門裏的東西、箱子裏的錢。也就是說現實世界的鎖是間接的控制資源。
  • 程序世界裏的鎖,則是一種更爲高級的抽象,它包含的對資源的各類維度的控制,咱們能夠將其理解爲「規則」,好比:某類資源在同一時刻只能有一個線程進行操做(同步性)、某類資源必須由多個線程同時操做(同步協做)、某類資源最多隻能有N個線程進行操做(資源調度許可證)。這些都是「規則」的運用。

2.2.鎖的種類

java中有關於鎖的內容很是多,咱們這裏先用一張圖來簡要介紹一下,之後再着重篇幅去介紹每一個鎖的相關特性。

圖片描述

2.3.關於資源的小結

  1. 任何實例對外提供的方法(儘可能避免對變量的直接操做)
  2. 咱們須要在對外提供的方法內用「鎖」去控制方法內的被調度的規則。
  3. 實行第二條以前必定要肯定當前的編程環境,是單線程的仍是多線程

3.線程池

用一段話形容線程和資源的關係那就是。某我的(線程)去作(調度)某件有要求和規則(鎖策略)的事(資源);根據這件事(資源)的要求和規則(鎖策略)去約束作這個事人(線程)的作法

咱們用各類鎖策略去保證資源能被正確的使用。這裏咱們還缺一個角度,那就是從線程的角度去調度資源。

咱們用一個問題開頭來展開對話。

  • 問:咱們能根據資源的數量去建立線程的數量嗎?
  • 答:不能,由於建立線程的開銷大,受機器的配置的限制。
  • 問:那麼能不能建立必定數量的線程,去循環的調度資源。
  • 答:這麼作是能夠的,可是資源數通常來講確定是多於線程數,咱們要控制資源的調度順序,還將來得及調度的資源能夠按先來後到原則存放到隊列。
  • 問:那資源數少於線程數時候,該怎麼樣去處理。
  • 答:咱們能夠保留必定量的線程,爲將來可能調度的資源作預備。

這就是一個線程池的雛形,線程池的雛形具備如下的基本特性

  1. 具備最大的線程數限制
  2. 有若干常備線程(核心線程)
  3. 資源數若多超過了最大線程數的限制則會放入隊列中。

咱們來解刨線程池的最全的配置信息:

  1. corePoolSize:核心線程數
  2. maximumPoolSize:最大的線程數
  3. keepAliveTime:無資源調度的線程回收的時間(默認單位:毫秒)
  4. TimeUnit:時間單位
  5. BlockingQueue:多餘的資源放入的隊列
  6. ThreadFactory:線程的工廠類
  7. RejectedExecutionHandler:線程池調度資源的策略

圖片描述

按照咱們常規的設置

BlockingQueue > maximumPoolSize ≥ corePoolSize

  • corePoolSize會隨着資源調度數增長至maximumPoolSize
  • 當線程空閒時,會根據keepAliveTime來回收線程數(maximumPoolSize-corePoolSize)
  • BlockingQueue分無界Queue和有界Queue。
  • 當資源調度大於maximumPoolSize時會放入BlockingQueue中
    • 當BlockingQueue是有界隊列則存入
    • 當BlockingQueue是無界隊列則根據策略調整
  • 當資源調度數大於BlockingQueue的長度,則根據RejectedExecutionHandler的策略來調整資源調度狀況。
    • AbortPolicy:默認策略,捨棄最新的資源調度,並拋出異常
    • DiscardPolicy:捨棄最新的資源調度,不會有異常
    • DiscardOldestPolicy:捨棄在隊列中隊頭的資源
    • CallerRunsPolicy:交由主線程去執行(慎用
    • 自定義拒絕策略:實現RejectedExecutionHandler接口,編寫特殊業務的拒絕策略。

總結:線程池就是多個線程來調度多個資源時所優化的一種多維度的策略,它的核心就是線程的複用以及資源的緩衝存儲。

經常使用的方法和工具

類或關鍵字 簡要介紹 使用的對象
synchronized 做用於方法或代碼塊中實現線程的同步性 資源
Atomic包 原子性控制變量或實例 資源
ReentrantLock 做用於方法中,實現線程的同步性 資源
Semaphore 做用於方法中,限制方法的線程最大調度數 資源
Phaser 做用於方法中,設置線程必須調度的數量 資源
Object.wait() 做用於同步的方法中,使當前調度的進程等待 資源
Object.notifyAll()或notify() 做用於同步的方法中,喚起當前處於等待狀態的調度線程 資源
Runable 線程中調度無返回結果的資源 線程
Callable 線程中調度有返回結果的資源 線程
Future 線程執行有返回結果的資源的接受方 線程
Executor 建立線程池的工廠類(慎用) 線程
ThreadPoolExecutor 線程池的實現類 線程
ExecutorService 線程池的基礎類 線程
CompletionService 異步處理帶返回結果的線程池 線程
ScheduledExecutorService 處理定時任務的線程池 線程
Fork-Join 分治思想處理批量任務 線程
相關文章
相關標籤/搜索