【併發編程系列1】Thread生命週期及interrupted()作用分析

什麼是線程

線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,一個進程中可以併發多個線程,每條線程並行執行不同的任務。線程的出現是爲了更加合理的利用CPU資源

如何創建線程

創建線程有2種方法,一種是繼承Thread類,另一種就是實現Runable

繼承Thread類

新建一個類繼承Thread類並重寫run()方法:

package com.zwx.thread;

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("111");
    }
}
package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

實現Runnable接口

新建一個類實現Runnable接口並重寫run()方法:

package com.zwx.thread;

public class MyThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println("111");
    }
}
package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
    }
}

創建線程簡寫形式

上面是線程的兩種基本形式,我們有時候創建線程也可以用下面的簡寫形式進行直接創建:

package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是t1");

            }
        },"t1");
        t1.start();

        new Thread(()->{
            System.out.println("我是t2");
        },"t2").start();
    }
}

線程的生命週期

線程中總共有六種狀態。

NEW

NEW表示線程尚未啓動的狀態,即當我們new 一個線程時,線程就是NEW狀態。

package com.zwx.thread;
 
public class ThreadState {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1線程");
        });
        System.out.println(t1.getState());
    }
}

上面輸出的狀態就是:NEW

RUNNABLE

可運行狀態或者說就緒狀態。當我們調用thread.start()之後,線程就進入RUNNABLE狀態,注意,調用start()方法之後線程並不一定會馬上執行,還要看操作系統的調度才能執行。

package com.zwx.thread;

public class ThreadState {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1線程");
        });
        System.out.println(t1.getState());//NEW
        t1.start();
        System.out.println(t1.getState());//RUNNABLE
    }
}

上面的第二個輸出語句中線程t1的狀態就是RUNNABLE狀態

TERMINATED

package com.zwx.thread;

public class ThreadState {
   public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1線程");
        });
        System.out.println(t1.getState());//NEW
        t1.start();
        Thread.sleep(1000);
        System.out.println(t1.getState());//TERMINATED
    }
}

t1線程啓動之後,我們把主線程休眠1s,t1執行完畢之後再看t1的狀態就是TERMINATED

WAITING

等待狀態,當調用wait(),join()或者park()方法時,線程就會處於WAITING等待狀態

package com.zwx.thread;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (true){
                synchronized (ThreadState.class){
                    System.out.println("我是t1線程");
                    try {
                        ThreadState.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t1").start();

        new Thread(()->{
            while (true){
                System.out.println("我是t2線程");
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }
}

運行之後通過jps和jstack命令可以看到t2線程就是TIMED_WAITING狀態,而t1就是WAITING狀態
在這裏插入圖片描述

TIMED_WAITING

和上面WAITING狀態一樣,只是有一個等待時間,一般調用如下方法時:sleep(),wait(),join(),LockSupport.parkNanos(),LockSupport.parkUntil(),而且方法帶上時間則線程會出現這個狀態。
示例同上面的WAITING中的示例

BLOCKED

線程等待鎖時被阻塞狀態。一個線程正在等待進入一個同步代碼塊時線程會被阻塞或者被調用之後重入一個同步代碼塊時被阻塞。

package com.zwx.thread;

public class ThreadState {
    static class BlockDemo extends Thread{
        @Override
        public void run() {
            synchronized (BlockDemo.class){
                while (true){

                }
            }
        }
    }
    public static void main(String[] args) {
        new Thread(new BlockDemo(),"t3").start();
        new Thread(new BlockDemo(),"t4").start();
    }
}

上面示例中t3和t4爭搶同一把鎖進入同步代碼塊,那麼一定會有一個線程處於BLOCKED狀態:
在這裏插入圖片描述

線程生命週期圖

經過上面的分析,我們可以得到一張線程的生命週期簡圖:
在這裏插入圖片描述

interrupted()作用

線程當中有三個關於中斷的方法:

  • interrupt():打一箇中斷標記,並沒有實際去中斷一個線程。
  • isInterrupted():如果調用了interrupt()方法,則這裏會輸出true,表示當前線程被中斷過。
  • interrupted():靜態方法。如果調用了interrupt()方法,則這裏會輸出true,表示當前線程被中斷過,但是這個方法比上面的isInterrupted()方法多做了一件事,那就是線程復位,也就是說,連續兩次調用interrupted()方法後,第一次爲true,第二次就會變回false。
    我們來看下面一個例子:
package com.zwx.thread.baseapi;

public class ThreadInterruptDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            int i=0;
            while(Thread.interrupted()){
                System.out.println("我是線程" + Thread.currentThread().getName() + ":" + Thread.interrupted());//false
            }
        },"t1");
        t1.start();
        t1.interrupt();
    }
}

這裏的輸出結果爲false,這是因爲判斷條件裏面調用了一次爲true,然後對線程進行了復位,所以run()方法內輸出了false。

實際上: isInterrupted()和 interrupted()方法最終都調用的是下面這個本地方法,只不過isInterrupted()方法默認傳了false(不復位),而 interrupted()默認傳了true(復位)
在這裏插入圖片描述
線程中斷標記有什麼用呢?

這是因爲我們並不建議直接去中斷一個線程,假如一個線程正在執行一系列操作,那麼強行中斷,可能會引起其他問題(這也是stop()方法之所以標記爲過期的原因),所以中斷只是打一個標記,然後由線程自己去獲取中斷標記,再決定要不要中斷。

就比如我們上面的while循環中,我們獲取到中斷標記,然後內部可以決定是繼續執行循環體還是直接返回終止線程。

爲什麼需要對線程復位?
我們設想這麼一種場景,假如我們規定收到3次線程中斷要求的時候,即使線程沒有執行完畢這時候也需要直接返回,那麼線程如果不復位,我們就沒辦法知道當前線程進行過多少次中斷(不考慮同時多次中斷的情況),因爲中斷過一次,一直是true;而有了線程復位,我們就只需要判斷3次都是true就說明至少被中斷過3次。

當interrupt()遇到sleep()

我們都知道,每次調用sleep()的時候都需要拋出一個InterruptedException異常
老規矩,還是來看一個例子:

package com.zwx.thread.baseapi;

public class InterruptSleepThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().isInterrupted());//false
            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

這裏輸出結果爲false,這就說明當我們一個休眠的線程被中斷後,拋出異常的同時也會對線程的中斷標記進行復位,這一點是需要注意的

currentThread()和this的區別

我們還是先來看下面的一個例子:

package com.zwx.thread.baseapi;

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//t1
        System.out.println(this.getName());//Thread-0
    }
}
package com.zwx.thread.baseapi;

public class CurrentThreadAndThis {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread,"t1");
        t1.start();
    }
}

輸出結果如下圖:
在這裏插入圖片描述
爲什麼this.getName()不會輸出t1,很多人的理解中這兩種應該是等價的。其實不然,this指的是當前對象,也就是MyThread對象,而currentThread()纔是指的當前線程t1。

首先我們看第一行代碼:

MyThread myThread = new MyThread();

因爲MyThread繼承了Thread,所以會調用默認的無參構造器:
在這裏插入圖片描述
這時候會產生一個name爲:Thread-0的線程。

然後第二行代碼我們再把這個線程傳進去重新new一個線程又會發生什麼?這時候調用的是另一個有參構造器:
在這裏插入圖片描述
我們發現,myThread作爲target被傳入進去,斷點後發現我們最開始創建的線程Thread-0被賦在t1對象的traget中:
在這裏插入圖片描述

實際上這產生了一個現象,最後的結果是t1線程中的target線程纔是我們的myThread線程,也就是最後執行run()方法的是target中的對象(myThread)去執行。所以this獲取到的名字是target中線程的名字,而currentThread()獲取到的是當前線程t1的名字。

那如果我們不創建t1線程,直接調用myThread.start()呢,這時候的結果兩個都會輸出Thread-0,因爲這時候this對象就是myThread,當前線程是myThread。

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