死磕 java線程系列之線程模型

  • 2019 年 10 月 6 日
  • 筆記

問題

(1)線程類型有哪些?

(2)線程模型有哪些?

(3)各語言使用的是哪種線程模型?

簡介

在Java中,我們平時所說的並發編程、多線程、共享資源等概念都是與線程相關的,這裡所說的線程實際上應該叫作「用戶線程」,而對應到操作系統,還有另外一種線程叫作「內核線程」。

用戶線程位於內核之上,它的管理無需內核支持;而內核線程由操作系統來直接支持與管理。幾乎所有的現代操作系統,包括 Windows、Linux、Mac OS X 和 Solaris,都支持內核線程。

最終,用戶線程和內核線程之間必然存在某種關係,本章我們一起來學習下建立這種關係常見的三種方法:多對一模型、一對一模型和多對多模型。

多對一模型

thread model

多對一線程模型,又叫作用戶級線程模型,即多個用戶線程對應到同一個內核線程上,線程的創建、調度、同步的所有細節全部由進程的用戶空間線程庫來處理。

優點:

  • 用戶線程的很多操作對內核來說都是透明的,不需要用戶態和內核態的頻繁切換,使線程的創建、調度、同步等非常快;

缺點:

  • 由於多個用戶線程對應到同一個內核線程,如果其中一個用戶線程阻塞,那麼該其他用戶線程也無法執行;

  • 內核並不知道用戶態有哪些線程,無法像內核線程一樣實現較完整的調度、優先級等;

許多語言實現的協程庫基本上都屬於這種方式,比如python的gevent。

一對一模型

thread model

一對一模型,又叫作內核級線程模型,即一個用戶線程對應一個內核線程,內核負責每個線程的調度,可以調度到其他處理器上面。

優點:

  • 實現簡單【本篇文章由公眾號「彤哥讀源碼」原創】;

缺點:

  • 對用戶線程的大部分操作都會映射到內核線程上,引起用戶態和內核態的頻繁切換;

  • 內核為每個線程都映射調度實體,如果系統出現大量線程,會對系統性能有影響;

Java使用的就是一對一線程模型,所以在Java中啟一個線程要謹慎。

多對多模型

thread model

多對多模型,又叫作兩級線程模型,它是博採眾長之後的產物,充分吸收前兩種線程模型的優點且盡量規避它們的缺點。

在此模型下,用戶線程與內核線程是多對多(m : n,通常m>=n)的映射模型。

首先,區別於多對一模型,多對多模型中的一個進程可以與多個內核線程關聯,於是進程內的多個用戶線程可以綁定不同的內核線程,這點和一對一模型相似;

其次,又區別於一對一模型,它的進程里的所有用戶線程並不與內核線程一一綁定,而是可以動態綁定內核線程, 當某個內核線程因為其綁定的用戶線程的阻塞操作被內核調度讓出CPU時,其關聯的進程中其餘用戶線程可以重新與其他內核線程綁定運行。

所以,多對多模型既不是多對一模型那種完全靠自己調度的也不是一對一模型完全靠操作系統調度的,而是中間態(自身調度與系統調度協同工作),因為這種模型的高度複雜性,操作系統內核開發者一般不會使用,所以更多時候是作為第三方庫的形式出現。

優點:

  • 兼具多對一模型的輕量;

  • 由於對應了多個內核線程,則一個用戶線程阻塞時,其他用戶線程仍然可以執行;

  • 由於對應了多個內核線程,則可以實現較完整的調度、優先級等;

缺點:

  • 實現複雜【本篇文章由公眾號「彤哥讀源碼」原創】;

Go語言中的goroutine調度器就是採用的這種實現方案,在Go語言中一個進程可以啟動成千上萬個goroutine,這也是其出道以來就自帶「高並發」光環的重要原因。

後面講到Java中的ForkJoinPool的時候,我們會拿Go語言的PMG線程模型來對比講解。

總結

(1)線程分為用戶線程和內核線程;

(2)線程模型有多對一模型、一對一模型、多對多模型;

(3)操作系統一般只實現到一對一模型;

(4)Java使用的是一對一線程模型,所以它的一個線程對應於一個內核線程,調度完全交給操作系統來處理;

(5)Go語言使用的是多對多線程模型,這也是其高並發的原因,它的線程模型與Java中的ForkJoinPool非常類似;

(6)python的gevent使用的是多對一線程模型;

彩蛋

你所學過的語言都是使用的什麼線程模型呢?

推薦閱讀

1、死磕 java集合系列

2、死磕 java原子系列

3、死磕 java同步系列


歡迎關注我的公眾號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一起暢遊源碼的海洋。

qrcode