Java線程知識筆記

如果使用得當,線程可以有效地降低程序的開發和維護等成本,同時提升複雜應用程序的性能。具體說,線程的優勢有:

Java線程知識筆記

Java線程知識筆記

  1、發揮多處理器的強大能力

現在,多處理器系統正日益盛行,並且價格不斷降低,即時在低端服務器和中斷桌面系統中,通常也會採用多個處理器,這種趨勢還在進一步加快,因爲通過提高時鐘頻率來提升性能已變得越來越困難,處理器生產廠商都開始轉而在單個芯片上放置多個處理器核。

試想,如果只有單個線程,雙核處理器系統上程序只能使用一半的CPU資源,擁有100個處理器的系統上將有99%的資源無法使用。多線程程序則可以同時在多個處理器上執行,如果設計正確,多線程程序可以通過提高處理器資源的利用率來提升系統吞吐率。

  2、在單處理器系統上獲得更高的吞吐率

如果程序是單線程的,那麼當程序等待某個同步I/O操作完成時,處理器將處於空閒狀態。而在多線程程序中,如果一個線程在等待I/O操作完成,另一個線程可以繼續運行,使得程序能在I/O阻塞期間繼續運行。

  3、建模的簡單性

通過使用線程,可以將複雜並且異步的工作流進一步分解爲一組簡單並且同步的工作流,每個工作流在一個單獨的線程中運行,並在特定的同步位置進行交互。我們可以通過一些現有框架來實現上述目標,例如Servlet和RMI,框架負責解決一些細節問題,例如請求管理、線程創建、負載平衡,並在正確的時候將請求分發給正確的應用程序組件。

編寫Servlet的開發人員不需要了解多少請求在同一時刻要被處理,也不需要了解套接字的輸入流或輸出流是否被阻塞,當調用Servlet的service方法來響應Web請求時,可以以同步的方式來處理這個請求,就好像它是一個單線程程序。

  4、異步事件的簡化處理

服務器應用程序在接受多個來自遠程客戶端的套接字連接請求時,如果爲每個連接都分配其各自的線程並且使用同步I/O,那麼就會降低這類程序的開發難度。如果某個應用程序對套接字執行讀操作而此時還沒有數據到來,那麼這個讀操作將一直阻塞,直到有數據到達。

在單線程應用程序中,這不僅意味着在處理請求的過程中將停頓,而且還意味着在這個線程被阻塞期間,對所有請求的處理都將停頓。

爲了避免這個問題,單線程服務器應用程序必須使用非阻塞I/O,但是這種I/O的複雜性要遠遠高於同步I/O,並且很容易出錯。然而,如果每個請求都擁有自己的處理線程,那麼在處理某個請求時發生的阻塞將不會影響其他請求的處理。

  知識回顧

進程與線程是常常被提到的兩個概念。進程擁有獨立的代碼段、數據空間,線程共享代碼段和數據空間,但有獨立的棧空間。線程是操作系統調度的最小單位,通常一個進程會包含一個或多個線程。

多線程和多進程都可以實現併發處理,如 nginx 使用多進程方式、tomcat 使用多線程方式、Apache 支持混合使用。在 C/C++ 等語言中可以同時使用多進程和多線程,而在 Java 中只能使用多線程。

在 Java 中,創建線程的唯一方式是創建 Thread 類的實例,調用實例的 start() 方法啓動線程。

  Java 線程實現

在 JDK 1.2 之前,Java 使用用戶線程實現 Java 線程,在 JDK 1.2 及之後,Java 基於操作系統原生的線程模型實現 Java 線程。

使用用戶線程( 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 種狀態中的一種,並在各個狀態之間切換,如圖所示。

其中,各個狀態含義如下:

新建:創建後未啓動;

運行:對於 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 種線程狀態:新建、運行、等待、限期等待、阻塞和結束。

每個虛擬機線程都有自己私有的虛擬機棧。棧與線程同時創建,其中存儲的是線程的棧幀。每個方法的調用,都對應着一個棧幀的入棧和出棧。每個線程擁有自己的工作內存,工作內存中保存了線程需要讀寫的變量的主內存的副本。線程對變量的讀寫操作都在工作內存中直接進行,並不會去操作主內存中的內容,主內存與工作內存的同步由虛擬機完成。