【精華】JAVA基礎知識——併發與多線程

一、基本概念

1.線程和進程

  進程: 是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。
  線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之爲多線程程序。
  總之,一個程序運行後至少有一個進程,一個進程中可以包含多個線程。

2.並行和併發

  並行: 兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。
  併發: 兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在運行。
在這裏插入圖片描述

3.鎖和同步

1)同步synchronized是一個關鍵詞,是來來修飾方法的,但是鎖lock是一個實例變量,通過調用lock()方法來取得鎖。
2)只能同步方法,而不能同步變量和類,鎖也是一樣。
3)同步無法保證線程取得方法執行的先後順序,鎖可以設置公平鎖來確保。
4)不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
5)如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制;鎖也是一樣。
6)線程睡眠時,它所持的任何鎖都不會釋放。
7)線程可以獲得多個鎖。比如,在一個對象的同步方法裏面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。
8)同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9)在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。
注:如果使用鎖,要注意避免編碼出現死鎖。

二、線程基礎知識

1.線程實現

  實現線程主要有3種方式:使用內核線程實現使用用戶線程實現使用用戶線程加輕量級進程混合實現

使用內核線程實現

  • 內核線程
  1. 內核線程是直接由操作系統內核支持的線程,由內核完成線程切換。
  2. 內核通過線程調度器(Scheduler)對線程調度並負責將線程的任務映射到各個處理器上。
  3. 每個內核線程(Kernel Level Thread)是內核的一個分身,支持多線程的內核叫多線程內核(Multi-Threads Kernel)。
  • 輕量級進程
  1. 輕量級進程(通常意義所講的線程)是內核線程的一種高級接口,只有先支持內核線程,纔能有輕量級進程。
  2. 這種輕量級進程與內核線程之間1:1的關係稱爲一對一的線程模型
  3. 由於內核線程的支持,每個輕量級進程都成爲一個獨立的調度單元,即使有一個輕量級進程在系統調用中阻塞了,也不會影響整個進程繼續工作。

  輕量級進程的侷限性:

  1. 輕量級進程基於內核線程實現,線程操作需要進行系統調用,而系統調用需要再用戶態和內核態來回切換,調用代價高。
  2. 輕量級進程需要內核線程支持,要消耗一定內核資源(內核棧空間),因此係統支持輕量級進程的數量有限。

使用用戶線程實現

  不支持多線程的操作系統DOS使用用戶線程實現多線程。

  1. 從廣義上來講,一個線程只要不是內核線程,就可以認爲是用戶線程(User Thread,UT),因此上文提到的輕量級進程也屬於用戶線程。
  2. 狹義上的用戶線程指的是完全建立在用戶空間的線程庫上,系統內核不能感知線程存在的實現。
  3. 用戶線程的建立、同步、銷燬和調度完全在用戶態中完成,不要內核的幫助。部分高性能數據庫中的多線程就是由用戶線程實現的。
  4. 這種進程與用戶線程之間1:N的關係稱爲一對多的線程模型

  優勢: 不需要系統內核支援。
  劣勢: 線程操作需要程序自己處理,實現複雜。難以使用內核提供的線程調度及處理器映射。

使用用戶線程加輕量級進程混合實現

  Unix系列操作系統使用用戶線程加輕量級進程混合實現。

  1. 用戶線程還是完全建立在用戶空間中,因此用戶線程的創建、切換、析構等操作依然廉價,並且可以支持大規模的用戶線程併發。
  2. 操作系統提供支持的輕量級進程則作爲用戶線程和內核線程之間的橋樑,這樣可以使用內核提供的線程調度功能及處理器映射,並且用戶線程的系統調用要通過輕量級線程來完成,大大降低了整個進程被完全阻塞的風險。
  3. 在這種混合模式中,用戶線程和輕量級進程的數量比是不確定的,即爲N:M的關係,這就是多對多的線程模型

1.JDK1.2 之前,使用用戶線程實現,JDK1.2之後線程模型基於操作系統原生線程實現。
2. Windows、Linux版都是使用基於內核線程的輕量級進程來實現線程。

用戶級線程和內核級線程的區別

  1. 內核支持線程是OS內核可感知的,而用戶級線程是OS內核不可感知的。
  2. 用戶級線程的創建、撤消和調度不需要OS內核的支持,是在語言(如Java)這一級處理的;而內核支持線程的創建、撤消和調度都需OS內核提供支持,而且與進程的創建、撤消和調度大體是相同的。
  3. 用戶級線程執行系統調用指令時將導致其所屬進程被中斷,而內核支持線程執行系統調用指令時,只導致該線程被中斷。
  4. 在只有用戶級線程的系統內,CPU調度還是以進程爲單位,處於運行狀態的進程中的多個線程,由用戶程序控制線程的輪換運行;在有內核支持線程的系統內,CPU調度則以線程爲單位,由OS的線程調度程序負責線程的調度。
  5. 用戶級線程的程序實體是運行在用戶態下的程序,而內核支持線程的程序實體則是可以運行在任何狀態下的程序。

3.線程調度

  線程調度:系統爲線程分配處理器使用權的過程,主要調度方式有兩種,分別是協同式線程調度(Cooperative Threads-Scheduling)和搶佔式線程調度(Preemptive Threads-Scheduling)。

  • 協同式線程調度【lua的協同例程】

  線程的執行時間由線程本身來控制,線程執行完之後要主動通知系統切換到另一個線程。

  • 搶佔式線程調度【Java的線程調度】

  每個線程由系統分配執行時間,線程切換不由線程本身決決定(Thread.yield()可以讓出時間,但是無法獲取執行時間)
  線程執行時間由系統控制,程序只能通過設置線程優先級建議系統給線程分配不同時間。

4.線程優先級

  雖然Java線程調度是系統自動完成的,但是我們還是可以“建議”系統給某些線程多分配一點執行時間,另外的一些線程則可以少分配一點——這項操作可以通過設置線程優先級來完成。
  每一個 Java 線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。Java語言一共設置了10個級別的線程優先級,Java 線程的優先級是一個整數,其取值範圍是 1 (Thread.MIN_PRIORITY )-10(Thread.MAX_PRIORITY )。默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。
  在兩個線程同時處於Ready狀態時,優先級越高的線程越容易被系統選擇執行。不過,線程優先級並不是太靠譜,原因是Java的線程是通過映射到系統的原生線程上來實現的,所以線程調度最終還是取決於操作系統。

三、多線程模型

1.生命週期

  Java 線程的生命週期包括創建,就緒,運行,阻塞,死亡 5 個狀態。一個 Java 線程總是處於這 5 個生命週期狀態之一,並在一定條件下可以在不同狀態之間進行轉換 。
在這裏插入圖片描述

  • 新建狀態

  使用 new 關鍵字和 Thread 類或其子類建立一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。

  • 就緒狀態

  當線程對象調用了start()方法之後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏線程調度器的調度。

  • 運行狀態

  如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它可以變爲阻塞狀態、就緒狀態和死亡狀態。

  • 阻塞狀態

  如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。可以分爲三種:

  等待阻塞: 運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。
  同步阻塞: 線程在獲取 synchronized 同步鎖失敗(因爲同步鎖被其他線程佔用)。
  其他阻塞: 通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態。

  • 死亡狀態

  一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。

2.線程模型

  Future模型、Fork&Join 模型、Actor消息模型、生產者消費者模型、Master-Worker模型

  • Future模型

  Future模型通常在使用的時候需要結合Callable接口配合使用。Future是把結果放在將來獲取,當前主線程並不急於獲取處理結果。允許子線程先進行處理一段時間,處理結束之後就把結果保存下來,當主線程需要使用的時候再向子線程索取。

  Callable是類似於Runnable的接口,其中call方法類似於run方法,所不同的是run方法不能拋出受檢異常沒有返回值,而call方法則可以拋出受檢異常並可設置返回值。兩者的方法體都是線程執行體。

  • Fork&Join 模型

  該模型是jdk中提供的線程模型。該模型包含遞歸思想和回溯思想,遞歸用來拆分任務,回溯用合併結果。 可以用來處理一些可以進行拆分的大任務。

  其主要是把一個大任務逐級拆分爲多個子任務,然後分別在子線程中執行,當每個子線程執行結束之後逐級回溯,返回結果進行彙總合併,最終得出想要的結果。這裏模擬一個摘蘋果的場景:有100棵蘋果樹,每棵蘋果樹有10個蘋果,現在要把他們摘下來。爲了節約時間,規定每個線程最多隻能摘10棵蘋樹以便於節約時間。各個線程摘完之後彙總計算總蘋果樹。

  • Actor消息模型

  actor模型屬於一種基於消息傳遞機制並行任務處理思想,它以消息的形式來進行線程間數據傳輸,避免了全局變量的使用,進而避免了數據同步錯誤的隱患。actor在接受到消息之後可以自己進行處理,也可以繼續傳遞(分發)給其它actor進行處理。在使用actor模型的時候需要使用第三方Akka提供的框架。

  • 生產者消費者模型

  生產者消費者模型都比較熟悉,其核心是使用一個緩存來保存任務
  開啓一個或多個線程來生產任務,然後再開啓一個或多個來從緩存中取出任務進行處理。這樣的好處是任務的生成和處理分隔開,生產者不需要處理任務,只負責向生成任務然後保存到緩存。而消費者只需要從緩存中取出任務進行處理。使用的時候可以根據任務的生成情況和處理情況開啓不同的線程來處理。比如,生成的任務速度較快,那麼就可以靈活的多開啓幾個消費者線程進行處理,這樣就可以避免任務的處理響應緩慢的問題。

Master-Worker模型
  master-worker模型類似於任務分發策略,開啓一個master線程接收任務,然後在master中根據任務的具體情況進行分發給其它worker子線程,然後由子線程處理任務。如需返回結果,則worker處理結束之後把處理結果返回給master。使用的時候也可以使用java Thread來實現該模型。

四、多線程的實現

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