進程和線程小程序
談到多線程,就得先講進程和線程的概念。瀏覽器
進程服務器
進程能夠理解爲受操做系統管理的基本運行單元。360瀏覽器是一個進程、WPS也是一個進程,正在操做系統中運行的".exe"均可以理解爲一個進程多線程
線程框架
進程中獨立運行的子任務就是一個線程。像QQ.exe運行的時候就有不少子任務在運行,好比聊天線程、好友視頻線程、下載文件線程等等。異步
爲何要使用多線程性能
若是使用得當,線程能夠有效地下降程序的開發和維護等成本,同時提高複雜應用程序的性能。具體說,線程的優點有:spa
一、發揮多處理器的強大能力操作系統
如今,多處理器系統正日益盛行,而且價格不斷下降,即時在低端服務器和中斷桌面系統中,一般也會採用多個處理器,這種趨勢還在進一步加快,由於經過提升時鐘頻率來提高性能已變得愈來愈困難,處理器生產廠商都開始轉而在單個芯片上放置多個處理器核。試想,若是隻有單個線程,雙核處理器系統上程序只能使用一半的CPU資源,擁有100個處理器的系統上將有99%的資源沒法使用。多線程程序則能夠同時在多個處理器上執行,若是設計正確,多線程程序能夠經過提升處理器資源的利用率來提高系統吞吐率。線程
二、在單處理器系統上得到更高的吞吐率
若是程序是單線程的,那麼當程序等待某個同步I/O操做完成時,處理器將處於空閒狀態。而在多線程程序中,若是一個線程在等待I/O操做完成,另外一個線程能夠繼續運行,使得程序能在I/O阻塞期間繼續運行。
三、建模的簡單性
經過使用線程,能夠將複雜而且異步的工做流進一步分解爲一組簡單而且同步的工做流,每一個工做流在一個單獨的線程中運行,並在特定的同步位置進行交互。咱們能夠經過一些現有框架來實現上述目標,例如Servlet和RMI,框架負責解決一些細節問題,例如請求管理、線程建立、負載平衡,並在正確的時候將請求分發給正確的應用程序組件。編寫Servlet的開發人員不須要了解多少請求在同一時刻要被處理,也不須要了解套接字的輸入流或輸出流是否被阻塞,當調用Servlet的service方法來響應Web請求時,能夠以同步的方式來處理這個請求,就好像它是一個單線程程序。
四、異步事件的簡化處理
服務器應用程序在接受多個來自遠程客戶端的套接字鏈接請求時,若是爲每一個鏈接都分配其各自的線程而且使用同步I/O,那麼就會下降這類程序的開發難度。若是某個應用程序對套接字執行讀操做而此時尚未數據到來,那麼這個讀操做將一直阻塞,直到有數據到達。在單線程應用程序中,這不只意味着在處理請求的過程當中將停頓,並且還意味着在這個線程被阻塞期間,對全部請求的處理都將停頓。爲了不這個問題,單線程服務器應用程序必須使用非阻塞I/O,可是這種I/O的複雜性要遠遠高於同步I/O,而且很容易出錯。然而,若是每一個請求都擁有本身的處理線程,那麼在處理某個請求時發生的阻塞將不會影響其餘請求的處理。
建立線程的方式
建立線程有兩種方式:
一、繼承Thread,重寫父類的run()方法。
public class MyThread00 extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在運行!"); } } }
public static void main(String[] args) { MyThread00 mt0 = new MyThread00(); mt0.start(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在運行!"); } }
看一下運行結果:
main在運行! Thread-0在運行! main在運行! Thread-0在運行! main在運行! Thread-0在運行! main在運行! Thread-0在運行! Thread-0在運行! main在運行!
看到main線程和Thread-0線程交替運行,效果十分明顯。
有可能有些人看不到這麼明顯的效果,這也很正常。所謂的多線程,指的是兩個線程的代碼能夠同時運行,而沒必要一個線程須要等待另外一個線程內的代碼執行完才能夠運行。對於單核CPU來講,是沒法作到真正的多線程的,每一個時間點上,CPU都會執行特定的代碼,因爲CPU執行代碼時間很快,因此兩個線程的代碼交替執行看起來像是同時執行的同樣。那具體執行某段代碼多少時間,就和分時機制系統有關了。分時系統把CPU時間劃分爲多個時間片,操做系統以時間片爲單位片爲單位各個線程的代碼,越好的CPU分出的時間片越小。因此看不到明顯效果也很正常,一個線程打印5句話原本就很快,可能在分出的時間片內就執行完成了。因此,最簡單的解決辦法就是把for循環的值調大一點就能夠了(也能夠在for循環里加Thread.sleep方法,這個以後再說)。
二、實現Runnable接口。和繼承自Thread類差很少,不過實現Runnable後,仍是要經過一個Thread來啓動:
public class MyThread01 implements Runnable { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在運行!"); } } }
public static void main(String[] args) { MyThread01 mt0 = new MyThread01(); Thread t = new Thread(mt0); t.start(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在運行!"); } }
效果也十分明顯:
main在運行! Thread-0在運行! main在運行! Thread-0在運行! main在運行! Thread-0在運行! main在運行! Thread-0在運行! main在運行! Thread-0在運行!
兩種多線程實現方式的對比
看一下Thread類的API:
其實Thread類也是實現的Runnable接口。兩種實現方式對比的關鍵就在於extends和implements的對比,固然是後者好。由於第一,繼承只能單繼承,實現能夠多實現;第二,實現的方式對比繼承的方式,也有利於減少程序之間的耦合。
所以,多線程的實現幾乎都是使用的Runnable接口的方式。不過,後面的文章,爲了簡單,就用繼承Thread類的方式了。
線程狀態
虛擬機中的線程狀態有六種,定義在Thread.State中:
一、新建狀態NEW
new了可是沒有啓動的線程的狀態。好比"Thread t = new Thread()",t就是一個處於NEW狀態的線程
二、可運行狀態RUNNABLE
new出來線程,調用start()方法即處於RUNNABLE狀態了。處於RUNNABLE狀態的線程可能正在Java虛擬機中運行,也可能正在等待處理器的資源,由於一個線程必須得到CPU的資源後,才能夠運行其run()方法中的內容,不然排隊等待
三、阻塞BLOCKED
若是某一線程正在等待監視器鎖,以便進入一個同步的塊/方法,那麼這個線程的狀態就是阻塞BLOCKED
四、等待WAITING
某一線程由於調用不帶超時的Object的wait()方法、不帶超時的Thread的join()方法、LockSupport的park()方法,就會處於等待WAITING狀態
五、超時等待TIMED_WAITING
某一線程由於調用帶有指定正等待時間的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就會處於超時等待TIMED_WAITING狀態
六、終止狀態TERMINATED
線程調用終止或者run()方法執行結束後,線程即處於終止狀態。處於終止狀態的線程不具有繼續運行的能力