天天看點

多線程筆記0406線程與程序生命周期Thread 簡介Runnable 簡介Thread 和 Runnable 示例

線程與程序

線程

一條線程指的是程序中一個單一順序的控制流,一個程序中可以并發多個線程,每條線程并行執行不同的任務。多線程是多任務的一種特别形式,但多線程使用了更小的資源開銷。

程序

一個程序包括有作業系統配置設定的記憶體空間,包含一個或多個線程。一個線程不能獨立的存在。它必須是程序的一部分。一個程序一直運作直到所有的非守護線程都結束運作後才能結束。

程序 線程
定義 程序是處于運作中的程式,并且具有一定的獨立功能。程序是系統進行資源配置設定和排程的一個機關。當程式進入内容時,即為程序。 線程是程序的組成部分,一個程序可以擁有多個線程,而一個線程必須擁有一個父程序。線程可以擁有自己的堆棧,自己的程式計數器和局部變量,但不能擁有系統資源。它與父程序的其他線程共享該程序的所有資源。
特點 1. 獨立性:程序是系統中獨立存在的實體,它可以獨立擁有資源,每一個程序都有自己獨立的位址空間,沒有程序本身的運作,使用者程序不可以直接通路其他程序的位址空間。2. 動态性:程序與程式的差別在于程序是動态的,程序中有時間的概念,程序具有自己的生命周期和各種不同的狀态。3. 并發性:多個程序可以在單個處理器上并發執行,互不影響。 1. 線程可以完成一定任務,可以和其他線程共享父程序的共享變量和部分環境,互相協作來完成任務。2. 線程是獨立運作的,其不知道程序中是否還有其他線程存在。3. 線程的執行時是搶占式,也就是說目前執行的線程随時可能被挂起,一邊運作另一個線程。4. 一個線程可以建立或撤銷另一個線程,一個程序中的多個線程可以并發執行。

生命周期

一個線程對象在它的生命周期内,需要經曆五種狀态:

  1. 新生狀态(New):使用 new 關鍵字建立一個線程對象後,該線程對象就處于新生狀态。處于新生狀态的線程有自己的記憶體空間,通過調用 start() 方法進入就緒狀态。
  2. 就緒狀态(Runnable):處于就緒狀态的線程已經具備了運作條件,但是還沒有被配置設定到CPU,處于“線程就緒隊列”,等待系統為其配置設定CPU。就緒狀态并不是執行狀态,當系統標明一個等待執行的Thread對象後,它就會進入執行狀态。一旦獲得CPU,線程就進入運作狀态并自動調用自己的run方法。
  3. 運作狀态(Running):在運作狀态的線程執行自己run方法中的代碼,直到調用其他方法而終止或等待某資源而阻塞或完成任務而死亡。如果在給定的時間片内沒有執行結束,就會被系統給換下來回到就緒狀态。也可能由于某些“導緻阻塞的事件”而進入阻塞狀态。
  4. 阻塞狀态(Blocked):阻塞指的是暫停一個線程的執行以等待某個條件發送(如某資源就緒)。
  5. 死亡狀态(Terminated):死亡狀态是線程聲明周期中的最後一個階段,當一個線程進入死亡狀态以後,就不能再回到其他狀态了。

導緻線程進入就緒狀态的原因

  1. 建立線程:調用 start() 方法,進入就緒狀态;
  2. 阻塞線程:阻塞解除,進入就緒狀态;
  3. 運作線程:調用 yield() 方法,直接進入就緒狀态;
  4. 運作線程:JVM将CPU資源從本線程切換到其他線程。

導緻線程進入阻塞狀态的原因

  1. 執行 sleep(int millsecond) 方法,使目前線程休眠,進入阻塞狀态。當指定的時間到了後,線程進入就緒狀态。
  2. 執行 wait() 方法,使目前線程進入阻塞狀态。當使用 nofity() 方法喚醒這個線程後,它進入就緒狀态。
  3. 線程運作時,某個操作進入阻塞狀态,比如執行IO流操作( read() / write() 方法本身就是阻塞的方法)。隻有當引起該操作阻塞的原因消失後,線程進入就緒狀态。
  4. join() 線程聯合:當某個線程等待另一個線程執行結束後,才能繼續執行時(使用 join() 方法)。

導緻線程進入死亡狀态的原因

  1. 正常運作的線程完成了 run() 方法内的全部工作;
  2. 當程式發生異常,線程抛出一個未捕獲的 Exception 或 Error;
  3. 線程被強制終止,如通過執行 stop() 或者 destroy() 方法來終止一個線程(該方法容易導緻死鎖,不推薦使用)。

Thread 簡介

Thread 類實作了 Runnable 接口,在 Thread 類中,有一些比較關鍵的屬性,比如:

  1. name 是表示 Thread 的名字,可以通過 Thread 類的構造器中的參數來指定線程名字;
  2. priority 表示線程的優先級(最大值為10,最小值為1,預設值為5);
  3. daemon 表示線程是否是守護線程;
  4. target 表示要執行的任務。

由于使用 Thread 需要繼承該類,限制了程式的擴充,一般不推薦使用 Thread。

Runnable 簡介

如果一個類繼承 Thread,則不适合資源共享。但是如果實作了 Runnable 接口的話,則很容易的實作資源共享。

實作 Runnable 接口比繼承 Thread 類具有以下優勢:

  1. 可以避免 JAVA 中的單繼承限制。
  2. 多個線程都是基于某一個Runnable對象建立的,它們會共享Runnable對象上的資源。
  3. 線程池隻能放入實作 Runnable 或 callable 類線程,不能直接放入繼承 Thread 的類。

一般推薦使用 Runnable。

Thread 和 Runnable 示例

下面通過示例更好的了解Thread和Runnable,借鑒網上一個比較具有說服性的例子:3個人一共賣10張門票。

Thread 示例

Java

class MyThread extends Thread {

    private int ticket = 10;
    private int count = 20;

    @Override
    public void run() {
        for (int i = 0; i < count; i++) {
            if (this.ticket > 0) {
                System.out.println(this.getName() + " 賣票:" + this.ticket--);
            }
        }
    }
}           

複制

啟動線程

Java

public static void main(String[] args) {

    // 啟動三個線程,t1,t2,t3,每個線程各賣10張票
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    MyThread t3 = new MyThread();

    t1.start();
    t2.start();
    t3.start();
}           

複制

運作結果

Code

Thread-0 賣票:10
Thread-0 賣票:9
Thread-0 賣票:8
Thread-0 賣票:7
Thread-0 賣票:6
Thread-0 賣票:5
Thread-0 賣票:4
Thread-0 賣票:3
Thread-0 賣票:2
Thread-0 賣票:1
Thread-1 賣票:10
Thread-1 賣票:9
Thread-1 賣票:8
Thread-1 賣票:7
Thread-1 賣票:6
Thread-1 賣票:5
Thread-1 賣票:4
Thread-2 賣票:10
Thread-1 賣票:3
Thread-2 賣票:9
Thread-1 賣票:2
Thread-2 賣票:8
Thread-1 賣票:1
Thread-2 賣票:7
Thread-2 賣票:6
Thread-2 賣票:5
Thread-2 賣票:4
Thread-2 賣票:3
Thread-2 賣票:2
Thread-2 賣票:1           

複制

說明

主線程 main 建立并啟動 3個 MyThread 子線程,每個子線程都各自賣出了10張門票。

Runnable 示例

Java

class MyRunnable implements Runnable {

    private int ticket = 10;
    private int count = 20;

    public void run() {
        for (int i = 0; i < count; i++) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 賣票:" + this.ticket--);
            }
        }
    }
}           

複制

啟動線程

Java

public static void main(String[] args) {

    MyRunnable myRunnable = new MyRunnable();
    // 啟動三個線程,t1,t2,t3(它們共用一個Runnable對象),這三個線程一共賣10張票
    Runnable target;
    Thread t1 = new Thread(myRunnable);
    Thread t2 = new Thread(myRunnable);
    Thread t3 = new Thread(myRunnable);

    t1.start();
    t2.start();
    t3.start();
}           

複制

運作結果

Code

Thread-0 賣票:10
Thread-0 賣票:9
Thread-0 賣票:8
Thread-0 賣票:7
Thread-0 賣票:6
Thread-1 賣票:5
Thread-0 賣票:4
Thread-0 賣票:2
Thread-0 賣票:1
Thread-1 賣票:3

---- 但會存在以下情況 ----
Thread-0 賣票:10
Thread-0 賣票:9
Thread-0 賣票:8
Thread-0 賣票:7
Thread-0 賣票:6
Thread-0 賣票:5
Thread-0 賣票:4
Thread-0 賣票:3
Thread-0 賣票:2
Thread-0 賣票:1
Thread-2 賣票:0
Thread-1 賣票:10           

複制

說明

主線程main建立并啟動3個子線程,而且這3個子線程都是基于“myRunnable這個Runnable對象”而建立的。運作結果是這3個子線程一共賣出了10張票。這說明它們是共享了MyRunnable接口的。