Java 線程的初識


文章目錄

   一、進程與線程

    1、什麼是進程

    2、什麼是線程

    3、線程和進程之間的關係

    4、進程和線程內存方法的知識

    5、線程的分類

   二、多線程存在的意義

    1、提高程序的執行效率

    2、充分利用cpu資源

   三、線程的創建

    1、使用Thread 類創建線程

    2、實現 Runnalbel 接口

  四、線程的運行狀態

    1、 被創建

    2、運行

    3、凍結(sleep和wait)

    4、阻塞(臨時狀態)

    5、消亡

   五、線程的基本屬性

   1、線程棧

   2、線程名

   3、獲取當前線程

   ...

一、進程與線程

1、什麼是進程

正在進行中的程序。 例如 : 迅雷就是一個進程。 每一進程都有一個執行路徑(或者叫控制單元)

2、什麼是線程

進程中的一個獨立控制單元(執行路徑),每一個進程中,必須至少建立一個線程來作爲這個程序運行的起點。
例如: 迅雷同時下載多個視頻文件。

3、進程與線程之間的關係

進程其實就是對線程的封裝(線程是進程中的內容,線程在控制着進程的執行),操作系統將進程分成多個線程後,這些線程可以在操作系統的管理下併發執行。

雖然線程宏觀上多個線程同時執行,但實際上這只是操作系統在障眼法。由於一塊cpu同時執行執行一條命令(多核除外),因此在擁有一塊cpu的計算機上不可能同時執行兩個任務。操作系統爲了提高程序的運行效率,在一個線程空閒時會撤下這個線程,並且讓其他線程來執行(線程調度)。
其實,就是cpu 在多個線程中快速切換,由於速度快,我們很難感知,所以看上去像是併發執行。

4、進程和線程內存方法的知識

系統存在多個進程,進程之間不共享內存,進程中的線程共享系統分配給這個進程的內存空間。

每個線程也擁有自己獨立的內存空間,這段空間叫 “內存棧” 。在x86平臺下,默認線程棧大小是12KB

5、線程的分類

主線程 : 每個進程有且僅有一個主線程,主線程是程序執行的入口(主線程的代碼放於main方法中)

子線程 : 主線程除外的其他執行路徑即爲子線程(子線程的代碼放於run() 方法中 )

二、線程存在的意義

1、提高程序的執行效率

2、充分利用cpu資源

三、線程的創建

1、通過Thread類創建線程

步驟如下 :
a. 定義類繼承Thread
b. 重寫無參run() 方法,並將要在子線程執行的代碼放於run()方法體內
c. 創建自定義類對象,調用自定義對象的start()方法,讓虛擬機調用線程的run方法。

例如:

定義子類繼承 Thread

class Demo extends Thread
{
   public void run()
   {
     // do something 
   }
}

創建線程

public static void main (Stringh[] args)
{

  Demo thread = new Demo();  // 創建線程
  thread.start();            // 啓動線程 並調用 run()

}

注意: 如果直接調用對象的run() 方法,任務不會在子線程執行。子線程創建了,但是並沒有運行。即 start() 方法的作用是:(1)啓動線程 (2)並執行線程的run方法

換句話說 : 多個線程在爭奪cpu的使用權,誰爭取到,誰執行(隨機性)。在某一時刻,只能有一個程序運行(多核除外)。cpu在做快速切換,以達到看上去是同時運行的效果。

2、實現 Runnalbel 接口

步驟如下 :
a. 自定義類實現Runnable接口,並重寫run()方法。
b. 建立一個Thread對象
c. 將Runnable 接口的子類對象作爲實際參數傳遞給Thread類的構造函數
d. 調用Thread 類的start 方法開啓線程

例如 :

實現Runnable 接口

class Demo implenention Runnable
{
  public void run()
  {
   //do something
  }
}

main函數

public static void main (Stringh[] args)
{

  Demo thread = new Demo();    // 創建線程
  new Thread(thread).start();  //啓動線程 並調用 run()

}

注意:第二種方法擴展性比較高,可以有自己的父類。又可以實現Runnable。避免單繼承的侷限性。

四、線程的運行狀態

1、 被創建

2、運行

3、消亡

4、凍結(seleep和wait)

5、阻塞

總結如圖下圖
這裏寫圖片描述

線程在創建後, 並不會立即執行run方法中的代碼,而是處理等待狀態。線程處於等待狀態時,可以通過Thread類的方法設置線程的各種屬性。
當調用start()方法後,線程開始執行run方法中的代碼,線程進入運行狀態。可以通過isAlive() 方法來判斷線程是否處於運行狀態。 當返回true時,線程處於運行狀態。當返回flase,可能處於等在狀態,也可能處於停止狀態。

例子: (線程創建、運行、消亡)

public class LifeCycle extends Thread
{
  public void run()
  {
    int i = 0;
    while((++n)< 1000);
  }

  public static void main (String[] args)
  {
    LifeCycle  thread = new LifeCycle();

    System.out.println(threat.isAlive());  // 測試線程是否處於活動狀態   flase

    thread.start(); //開始線程

    System.out.println(threat.isAlive());  // 測試線程是否處於活動狀態   true 

    thread.join();  // 等待該線程終止,即run方法執行結束,線程消亡 (或理解成退出狀態完成)

    System.out.println("線程結束了");

    System.out.println(thread.isAlive());    // 測試線程是否處於活動狀態   flase
  }
}

線程的掛起和喚醒: 即等待狀態,線程暫時進入等在狀態。當等待時間到了,線程可能處於臨時狀態(阻塞狀態),也可能處於運行狀態。

例如:

public class ThreatSleep extends Thread
{
     public void run()
     {
         try 
         {
            sleep(2000); 

            System.out.println("2s後可能執行此行代碼");

        }
      catch (InterruptedException e) 
        {

            e.printStackTrace();
        }

     }

    public static void main(String[] args) {

        ThreatSleep thread = new ThreatSleep();
        thread.start();

    }

注意 : sleep() 只對當前正在執行的線程起作用。不能再一個線程中休眠另一個線程。例如: 在main方法中使用thread.sleep(2000)方法是無法使thread線程休眠2秒,而只能使主線程休眠2秒。

線程的消亡 : 線程任務執行完成後,自動消亡。 可理解成run()方法執行結束。
在java中一般有3種方法終止線程:

(1`) 使用退出標誌,使線程正常退出,也就是當run方法完成後終止線程
(2) 使用interrupt() 方法終止線程
(3) 使用stop() 方法強行終止線程(不推薦使用,可能會發生不可預料的後果)

  當執行run()方法完成後,線程就會退出,但是有時候run()方法是永遠都不會結束的。例如在服務端程序中使用線程進行監聽客戶端的請求,或者其他是需要循環處理的任務,在這種情況下,一般是將這些任務放在一個循環中,例如while(true){..}來處理。但是如果想使while循環在特定條件下退出,最直接的情況是設置一個boolean類型的標誌位,並通過設置這個標誌位爲true或flase來控制while循環是否退出

例如:

public class ThreadStop extends Thread{

    // volatile 使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值
    public volatile boolean exit = false;

    public void run()
    {
        while(!exit){

            System.out.println(this.getName());
        }
    }

    public static void main(String[] args) throws InterruptedException 
    {

        ThreadStop thread = new ThreadStop();
        thread.start();
        sleep(1000); //主線程睡眠1s,下面的代碼延遲1s後執行
        thread.exit = true;   
        thread.join(); // 等待thread線程執行完成再繼續執行下面的代碼
        System.out.println(thread.isAlive());

    }

}

使用stop()方法可以強行終止正在運行或者掛起的線程。但是使用stop()方法是很危險,就像突然關閉計算機電源,可能會產生不可預料的結果。因此不推薦使用stop()方法來終止線程。

在使用interrupt()方法終止線程時可分爲下面兩種情況
( 1 ) 線程處於阻塞狀態,如使用sleep()方法
( 2 ) 使用while(! isInterrupted){….} 來判斷線程是否被中斷

上述第一種情況下使用interrupt方法,sleep方法將判處一個InterruptedException ,而第二種情況下線程將直接退出。

補充 : Thread有兩個方法可以判斷線程是否是通過interrupt()方法被終止。一個是靜態方法interrupted(),另一個是非靜態方法isInterrupted()。前者用來判斷當前線程是否被中斷,後者則可以判斷其他線程是否被中斷。

例如:

public class ThreadStop extends Thread {

    public void run()
    {
        try
        {
            sleep(50000);
        }
        catch (InterruptedException e)
        {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException 
    {
        ThreadStop thread = new ThreadStop();

        thread.start();

        System.out.println("在50s內按任意鍵中斷線程");

        System.in.read();

        System.out.println("0..."+thread.isAlive());

        thread.interrupt();

        //判斷thread線程是否是通過 interrupt 被終止的
        if(thread.isInterrupted())
            System.out.println("是的,我被interrupt終止了");

        System.out.println("1..."+thread.isAlive()); //線程被打斷後,不是立即就掛了

        thread.join(); //等待線程 終止狀態完成 後 再繼續執行下面的代碼

        System.out.println("2..."+thread.isAlive());

        System.out.println("線程已經退出");

    }

}

備註: isAlive() 可以用來測試某條線程是否已經死亡。當線程處於就緒、運行、或者阻塞3種狀態時,該方法返回true。 當線程處於創建、死亡狀態時,該方法返回false。

線程死亡 : 線程可以使用下面3種方式來結束線程,結束後的線程處於死亡狀態。

( 1 ) run() 方法執行完成,線程正常結束
( 2 ) 線程拋出一個未捕獲的異常 Exception 或 Error
( 3 ) 直接調用該線程的stop() 方法結束該線程,因爲該方法容易導致死鎖,所以不推薦使用。

注意 : 不要試圖對一個已經死亡的線程調用start()方法使他重新啓動,死亡就是死亡,該線程將不可再次作爲線程執行。

線程阻塞 : 當線程開始運行後,它不可能一直處於運行狀態,除非他的線程執行時間體足夠短,瞬間就執行結束了。線程在運行過程中被中斷,目的是使其他線程獲得執行的機會,線程的調度細節取決於平臺所採用的策略。對於採用搶佔式策略的系統而言,系統會給每個可執行的線程一個小時間段來處理任務,當該時間段用完,系統就會剝奪該線程所佔用的資源,讓其他線程獲得執行權,在選擇下一個線程時,系統會優先考慮線程的優先級。所有現代的桌面和服務器操作系統都是採用搶佔式調度策略,但一些小型設備入手機纔會採用協作式調度,在這樣的系統中,只有當一個線程調用了自身的sleep()或yield()方法後纔會放棄所佔用的資源(就是必須由該線程主動放棄所佔用的資源)

在計算機中,當發生如下狀態時線程就會進入阻塞狀態
(1) 線程調用了sleep()方法主動放棄所佔用的處理器資源
( 2 ) 線程調用了一個阻塞式 I/O 方法,在該方法返回之前,該線程被阻塞
(3) 線程正在等待某一個通知(notify)
(4) 當前正在執行的線程別阻塞後,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的機會重新進入就緒狀態。注意,就緒狀態而不是運行狀態。也就是說被阻塞線程的阻塞解除後,必須重新登臺線程調度器再次調度他。
(5)線程試圖獲得一個同步監視器,但該監視器正在被其他線程所持有

線程死亡: 線程執行任務結束
(1)run 方法執行完成,線程正常結束
(2) 線程拋出一個未捕獲的Exception或Error
( 3 ) 直接調用線程的stop() 方法 結束線程。

注意 : 對已經死亡的線程,不可將該線程再次作爲線程執行。否則會拋出ILLegalThreadStateException異常。

五、線程的基本屬性

1、線程棧

系統中的進程是在各自獨立的內存中運行的,進程中的線程共享系統分配給這個進程的內存空間,而且線程還擁 有自己的內存空間,這段內存空間稱爲”線程棧”

2、線程名

線程都有自己默認的名稱,Thread-編號,編號從0開始。用getName()獲取。
當然,你也可以自定義名稱,用setName(String name) 設置。當然,你也可以通過構造方法去設置。

主線程名字爲: main

3、獲取當前線程

Thread.currentThread()

currentThread() 是Thread類靜態方法,該方法總是返回當前正在執行的線程對象

4、判斷線程是否處於運行狀態

thread.isAlive(), 返回 true,線程處於運行狀態。 返回false,線程處於等待狀態,也可能處於停止狀態。

5、使用join() 方法

使異步執行的線程變成同步執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章