知識回顧
進程與線程是經常被提到的兩個概念。進程擁有獨立的代碼段、數據空間,線程共享代碼段和數據空間,但有獨立的棧空間。線程是操做系統調度的最小單位,一般一個進程會包含一個或多個線程。多線程和多進程均可以實現併發處理,如 nginx 使用多進程方式、tomcat 使用多線程方式、Apache 支持混合使用。在 C/C++ 等語言中能夠同時使用多進程和多線程,而在 Java 中只能使用多線程。nginx
在 Java 中,建立線程的惟一方式是建立 Thread 類的實例,調用實例的 start() 方法啓動線程。程序員
Java 線程實現
在 JDK 1.2 以前,Java 使用用戶線程實現 Java 線程,在 JDK 1.2 及以後,Java 基於操做系統原生的線程模型實現 Java 線程。tomcat
使用用戶線程( User Thread, UT ) 實現,是指線程創建在用戶態空間,線程的創建、同步、調度與銷燬都在用戶態完成,進程與用戶線程之間是1 : N 的對應關係。這種狀況下,內核沒法知道有多少個用戶線程,實現較爲複雜。安全
使用內核線程實現,是指基於輕量級進程( Light Weight Process, LWP ) 來實現線程。每一個輕量級進程都有一個內核線程( Kernel-Level Thread, KLT ) 支持,與內核線程之間是 1 : 1 的對應關係。這種狀況下,調度線程時可能須要在內核態和用戶態之間進行切換。因爲輕量級進程須要消耗內核資源,可以支持的線程數量是有限的。微信
如在 Windows 和 Linux 系統中,操做系統原生的線程模型是 1 : 1 的對應關係,對於 Sun JDK 來講,一個 Java 線程就對應着一個輕量級進程。多線程
線程調度與狀態
在 Java中線程的調度方式是搶佔式調度,即由系統來負責各個線程的時間分配,並在線程使用完分配的時間後調度下一個線程。任何一個線程都不能獨佔 CPU 。Java 語言一共設置了 10 個線程優先級,當兩個線程同時等待執行時,優先級高的先被調度。線程的優先級會被映射到操做系統原生線程上去,但各個操做系統的優先級劃分不徹底同樣,所以兩個優先級不一樣的 Java 線程在操做系統中執行時也可能處於相同的優先級。併發
Java 定義了 5 種線程狀態,分別是新建 ( New )、運行 ( Running )、等待 ( Waiting )、限期等待 ( Timed Waiting )、阻塞 ( Blocked ) 和結束 ( Terminated )。任一時刻,線程都處於 5 種狀態中的一種,並在各個狀態之間切換,如圖所示。框架
其中,各個狀態含義以下:分佈式
新建:建立後未啓動;post
運行:對於 Java 來講,線程已經運行,但對於操做系統來講,可能在運行或等待;
等待:線程等待被其餘線程喚醒,如調用了 wait、join 且沒有指定超時時間;
限期等待:線程等待一段時間後被系統喚醒,如調用了 sleep、wait、join 並設置了超時時間;
阻塞:線程進入同步區域須要與其餘線程協調同步,如須要進入 synchronized 區域但其餘線程還沒有退出此區域;
結束:run 方法執行完成後,線程結束。
虛擬機棧
在 Java 內存模型中,每一個虛擬機線程都有本身私有的虛擬機棧。棧與線程同時建立,其中存儲的是線程的棧幀 ( Stack Frame )。每一個方法的調用,都對應着一個棧幀的入棧和出棧。在棧幀中,存儲着局部變量表 ( Local Variable Table )、操做棧 ( Operand Stack )、動態鏈接 ( Dynamic Linking )、返回地址 ( Return Address ) 和其餘附加信息。
線程的工做內存
在內存模型中,Java 要求全部的變量都必須存儲在主內存中,每一個線程擁有本身的工做內存。工做內存中保存了線程須要讀寫的變量的主內存的副本。線程對變量的讀寫操做都在工做內存中直接進行,並不會去操做主內存中的內容,主內存與工做內存的同步由虛擬機完成。不一樣線程不能訪問彼此的工做內存,變量值的傳遞須要通過主內存才能完成。
Volatile 修飾的變量能夠保證變量對全部線程可見,即某個線程修改變量後,其餘線程總能馬上讀到新值。即使如此,多線程併發時,對 volatile 變量進行自增自減操做也不能保證線程安全。
總結
線程在 Java 中只能經過建立 Thread 類的實例來建立。在 JDK 1.2 以後,Java 中的線程基於操做系統原生的線程模型來實現線程。線程的調度方式是搶佔式調度,即由系統來負責各個線程的時間分配,並在線程使用完分配的時間後調度下一個線程。Java 定義了 5 種線程狀態:新建、運行、等待、限期等待、阻塞和結束。
每一個虛擬機線程都有本身私有的虛擬機棧。棧與線程同時建立,其中存儲的是線程的棧幀。每一個方法的調用,都對應着一個棧幀的入棧和出棧。每一個線程擁有本身的工做內存,工做內存中保存了線程須要讀寫的變量的主內存的副本。線程對變量的讀寫操做都在工做內存中直接進行,並不會去操做主內存中的內容,主內存與工做內存的同步由虛擬機完成。
每週 3 篇學習筆記或技術總結,面向有必定基礎的 Java 程序員,內容涉及 Java 進階、虛擬機、MySQL、NoSQL、分佈式計算、開源框架等多個領域。關注做者或微信公衆號 backend-develop 第一時間獲取最新內容。
原文地址:Java線程知識拾遺