【多線程系列】進程與線程概述

進程和線程

談到多線程,就得先講進程和線程的概念。

進程

進程可以理解爲受操作系統管理的基本運行單元。360瀏覽器是一個進程、WPS也是一個進程,正在操作系統中運行的".exe"都可以理解爲一個進程。


線程

進程中獨立運行的子任務就是一個線程。像QQ.exe運行的時候就有很多子任務在運行,比如聊天線程、好友視頻線程、下載文件線程等等。

 

爲什麼要使用多線程

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

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,並且很容易出錯。然而,如果每個請求都擁有自己的處理線程,那麼在處理某個請求時發生的阻塞將不會影響其他請求的處理。

創建線程的方式

創建線程有兩種方式:

1、繼承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方法,這個之後再說)。

2、實現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中:

1、新建狀態NEW

new了但是沒有啓動的線程的狀態。比如"Thread t = new Thread()",t就是一個處於NEW狀態的線程

2、可運行狀態RUNNABLE

new出來線程,調用start()方法即處於RUNNABLE狀態了。處於RUNNABLE狀態的線程可能正在Java虛擬機中運行,也可能正在等待處理器的資源,因爲一個線程必須獲得CPU的資源後,纔可以運行其run()方法中的內容,否則排隊等待

3、阻塞BLOCKED

如果某一線程正在等待監視器鎖,以便進入一個同步的塊/方法,那麼這個線程的狀態就是阻塞BLOCKED

4、等待WAITING

某一線程因爲調用不帶超時的Object的wait()方法、不帶超時的Thread的join()方法、LockSupport的park()方法,就會處於等待WAITING狀態

5、超時等待TIMED_WAITING

某一線程因爲調用帶有指定正等待時間的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就會處於超時等待TIMED_WAITING狀態

6、終止狀態TERMINATED

線程調用終止或者run()方法執行結束後,線程即處於終止狀態。處於終止狀態的線程不具備繼續運行的能力。


發佈了42 篇原創文章 · 獲贊 13 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章