一文帶你讀懂:系統線程模型與實現原理


點擊上方藍字關注咱們css





各類操做系統均提供了線程的實現(內核線程),線程是 CPU 進行工做調度的基本單位
線程是比進程更輕量級的調度執行單位,線程的引入,能夠把一個進程的資源分配和執行調度分開,各個線程既能夠共享進程資源(內存地址、文件I/O等),又能夠獨立調度(線程是CPU調度的基本單位)。而編程語言通常都會提供操做內核線程的 API, Java 也不例外。
操做內核線程的模型主要有以下三種:
  1. 使用內核線程(1:1 模型)java

  2. 使用用戶線程(1:N 模型)web

  3. 使用用戶線程 + 輕量級進程(LWP)(N:M 模型)編程



基礎概念複習



咱們先複習下操做系統中的幾個關鍵概念:微信

  • 內核線程 KLT:內核級線程(Kemel-Level Threads, KLT 也有叫作內核支持的線程),直接由操做系統內核支持,線程建立、銷燬、切換開銷較大
  • 用戶線程 UT:用戶線程(User Thread,UT),創建在用戶空間,系統內核不能感知用戶線程的存在,線程建立、銷燬、切換開銷小
  • 輕量級進程 LWP (LWP,Light weight process)用戶級線程和內核級線程之間的中間層,是由操做系統提供給用戶的操做內核線程的接口的實現 。
  • 進程 P:用戶進程多線程




操做系統的三種線程模型






下面依次介紹三種線程模型:併發

  • 內核線程模型:

    內核線程模型即徹底依賴操做系統內核提供的內核線程(Kernel-Level Thread ,KLT)來實現多線程。在此模型下,線程的切換調度由系統內核完成,系統內核負責將多個線程執行的任務映射到各個CPU中去執行。app

    程序通常不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程(Light Weight Process,LWP),輕量級進程就是咱們一般意義上所講的線程,因爲每一個輕量級進程都由一個內核線程支持,所以只有先支持內核線程,纔能有輕量級進程。這種輕量級進程與內核線程之間1:1的關係稱爲一對一的線程模型。異步



  • 用戶線程模型:

    從廣義上來說,一個線程只要不是內核線程,就能夠認爲是用戶線程(User Thread,UT),所以,從這個定義上來說,輕量級進程也屬於用戶線程,但輕量級進程的實現始終是創建在內核之上的,許多操做都要進行系統調用,效率會受到限制。編程語言

    使用用戶線程的優點在於不須要系統內核支援,劣勢也在於沒有系統內核的支援,全部的線程操做都須要用戶程序本身處理。線程的建立、切換和調度都是須要考慮的問題,並且因爲操做系統只把處理器資源分配到進程,那諸如「阻塞如何處理」、「多處理器系統中如何將線程映射到其餘處理器上」這類問題解決起來將會異常困難,甚至不可能完成。

    於是使用用戶線程實現的程序通常都比較複雜,此處所講的「複雜」與「程序本身完成線程操做」,並不限制程序中必須編寫了複雜的實現用戶線程的代碼,使用用戶線程的程序,不少都依賴特定的線程庫來完成基本的線程操做,這些複雜性都封裝在線程庫之中,除了之前在不支持多線程的操做系統中(如DOS)的多線程程序與少數有特殊需求的程序外,如今使用用戶線程的程序愈來愈少了,Java、Ruby等語言都曾經使用過用戶線程,最終又都放棄使用它



  • 混合線程模型:

    線程除了依賴內核線程實現和徹底由用戶程序本身實現以外,還有一種將內核線程與用戶線程一塊兒使用的實現方式。在這種混合實現下,既存在用戶線程,也存在輕量級進程。

    用戶線程仍是徹底創建在用戶空間中,所以用戶線程的建立、切換、析構等操做依然廉價,而且能夠支持大規模的用戶線程併發。而操做系統提供支持的輕量級進程則做爲用戶線程和內核線程之間的橋樑,這樣可使用內核提供的線程調度功能及處理器映射,而且用戶線程的系統調用要經過輕量級線程來完成,大大下降了整個進程被徹底阻塞的風險。

    在這種混合模式中,用戶線程與輕量級進程的數量比是不定的,即爲N:M的關係。許多UNIX系列的操做系統,如Solaris、HP-UX等都提供了N:M的線程模型實現。

    對於Sun JDK來講,它的Windows版與Linux版都是使用一對一的線程模型實現的,一條Java線程就映射到一條輕量級進程之中,由於Windows和Linux系統提供的線程模型就是一對一的。在Solaris平臺中,因爲操做系統的線程特性能夠同時支持一對一(經過Bound Threads或Alternate Libthread實現)及多對多(經過LWP/Thread Based Synchronization實現)的線程模型,所以在Solaris版的JDK中也對應提供了兩個平臺專有的虛擬機參數:-XX:+UseLWPSynchronization(默認值)和-XX:+UseBoundThreads來明確指定虛擬機使用哪一種線程模型。






操做系統的線程調度方式



線程調度是指系統爲線程分配處理器使用權的過程

主要的線程調度方式有兩種,分別是 協同式線程調度(Cooperative Threads-Scheduling)和 搶佔式線程調度(Preemptive Threads-Scheduling),見下圖。



協同式調度
若是使用協同式調度的多線程系統,線程的執行時間由線程自己來控制,線程把本身的工做執行完了以後,要主動通知系統切換到另一個線程上
協同式多線程的最大好處是實現簡單,並且因爲線程要把本身的事情幹完後纔會進行線程切換,切換操做對線程本身是可知的,因此沒有什麼線程同步的問題。
Lua語言中的「協同例程」就是這類實現。它的壞處也很明顯:線程執行時間不可控制,甚至若是一個線程編寫有問題,一直不告知系統進行線程切換,那麼程序就會一直阻塞在那裏
好久之前的Windows 3.x系統就是使用協同式來實現多進程多任務的,至關不穩定,一個進程堅持不讓出CPU執行時間就可能會致使整個系統崩潰。

搶佔式調度

若是使用搶佔式調度的多線程系統,那麼每一個線程將由系統來分配執行時間,線程的切換不禁線程自己來決定(在Java中,Thread.yield()可讓出執行時間,可是要獲取執行時間的話,線程自己是沒有什麼辦法的)。

在這種實現線程調度的方式下,線程的執行時間是系統可控的,也不會有一個線程致使整個進程阻塞的問題。

Java使用的線程調度方式就是搶佔式調度。在JDK後續版本中有可能會提供協程(Coroutines)方式來進行多任務處理

與前面所說的Windows 3.x的例子相對,在Windows 9x/NT內核中就是使用搶佔式來實現多進程的,當一個進程出了問題,咱們還可使用任務管理器把這個進程「殺掉」,而不至於致使系統崩潰。


線程優先級

雖然Java線程調度是系統自動完成的,可是咱們仍是能夠「建議」系統給某些線程多分配一點執行時間,另外的一些線程則能夠少分配一點——這項操做能夠經過設置線程優先級來完成。

Java語言一共設置了10個級別的線程優先級(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在兩個線程同時處於Ready狀態時,優先級越高的線程越容易被系統選擇執行。

不過,線程優先級並非太靠譜,緣由是Java的線程是經過映射到系統的原生線程上來實現的,因此線程調度最終仍是取決於操做系統,雖然如今不少操做系統都提供線程優先級的概念,可是並不見得能與Java線程的優先級一一對應。

如Solaris中有2147483648(232)種優先級,但Windows中就只有7種,比Java線程優先級多的系統還好說,中間留下一點空位就能夠了,但比Java線程優先級少的系統,就不得不出現幾個優先級相同的狀況了。



上圖顯示了Java線程優先級與Windows線程優先級之間的對應關係,Windows平臺的JDK中使用了除THREAD_PRIORITY_IDLE以外的其他6種線程優先級。


Java 線程狀態


Java語言定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有其中的一種狀態,這5種狀態分別以下:



  1. 新建(New):建立後還沒有啓動的線程處於這種狀態。

  2. 運行(Runable):Runable包括了操做系統線程狀態中的Running和Ready,也就是處於此狀態的線程有可能正在執行,也有可能正在等待着CPU爲它分配執行時間。

  3. 無限期等待(Waiting):處於這種狀態的線程不會被分配CPU執行時間,它們要等待被其餘線程顯式地喚醒。


    如下方法會讓線程陷入無限期的等待狀態:沒有設置Timeout參數的Object.wait()方法。沒有設置Timeout參數的Thread.join()方法。LockSupport.park()方法。


  4. 限期等待(Timed Waiting):處於這種狀態的線程也不會被分配CPU執行時間,不過無須等待被其餘線程顯式地喚醒,在必定時間以後它們會由系統自動喚醒。

    如下方法會讓線程進入限期等待狀態:Thread.sleep()方法。設置了Timeout參數的Object.wait()方法。設置了Timeout參數的Thread.join()方法。LockSupport.parkNanos()方法。LockSupport.parkUntil()方法。


  5. 阻塞(Blocked):線程被阻塞了,「阻塞狀態」與「等待狀態」的區別是:「阻塞狀態」在等待着獲取到一個排他鎖,這個事件將在另一個線程放棄這個鎖的時候發生;而「等待狀態」則是在等待一段時間,或者喚醒動做的發生。在程序等待進入同步區域的時候,線程將進入這種狀態。


  6. 結束(Terminated):已終止線程的線程狀態,線程已經結束執行。



Java 多線程實現






實現1:繼承Thread類

// 繼承 Threadpublic class MyThread extends Thread { @Override public void run() { System.out.println("MyThread run..."); }}



實現2:實現 Runnable 接口

public class MyRunnable implements Runnable { @Override public void run() { System.out.println("MyRunnable run..."); }}

實現3:實現 Callable 接口,使用 FutureTask 獲取異步返回值

 public static void main(String[] args) throws ExecutionException, InterruptedException { class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "MyCallable"; } } FutureTask<String> task = new FutureTask<>(new MyCallable()); Thread c = new Thread(task); c.start(); System.out.println(task.get()); }


實現4:JDK8以上版本使用 CompletableFuture 進行異步計算。

在Java8中,提供了很是強大的Future的擴展功能,能夠幫助咱們簡化異步編程的複雜性,而且提供了函數式編程的能力,能夠經過回調的方式處理計算結果,也提供了轉換和組合 CompletableFuture 的方法。

public class CompletableFutureTest { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(2); // JDK1.8 提供的 CompletableFuture CompletableFuture<String> futureTask = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { System.out.println("task start"); try { Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); return "execute failure"; } System.out.println("task end"); return "execute success"; } }, threadPool); // 異步獲取 futureTask 的執行結果,此處代碼能夠跟其餘流程代碼放在一塊兒 futureTask.thenAccept(e-> System.out.println("future task result:" + e)); System.out.println("main thread end"); }}
輸出結果:task startmain thread endtask endfuture task result:execute success

實現5:使用線程池,ThreadPoolExecutor 類

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);}<T> Future<T> submit(Callable<T> task);Future<?> submit(Runnable task);




總結




本節主要講述了操做系統提供的三種線程模型和兩種線程調度方式,同時補充了基於Java的5種多線程實現和6個線程狀態的相關知識。

但願各位回顧知識的同時也有新收穫。GoodLuck!




END




掃描二維碼

獲取技術乾貨

後臺技術匯




點個「在看」表示朕

已閱



本文分享自微信公衆號 - 後臺技術匯(gh_bbd0c11cb61f)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索