Java線程的6種狀態詳解及建立線程的4種方式

在這裏插入圖片描述
某一天你在面試時遇到了線程的相關問題。
面試官:「你知道有哪幾種建立線程的方式嗎?」
(此時你的心理活動:哈哈小意思這能難住我,忍住激動僞裝淡定道)
你:「嗯,能夠經過實現 Runnable 接口和繼承 Thread 類來建立線程。」
面試官:「除了這兩種還有其餘方式嗎?」
你:「emmm...還有嗎?」
面試官:「知道經過實現 Callable 接口與獲取 Future 對象來實現嗎?」
你:「emmm不知道...不過如今知道了嘻嘻」
面試官:「那建立線程池有哪些方式呢?」
你:「能夠經過 ThreadPoolExecutor 構造函數或者 Executors 提供的工廠方法來建立」
面試官:「那經過不一樣的 Executors 工廠方法建立線程池之間有什麼區別呢?」
你:「emmm...「
面試官:「那 ThreadPoolExecutor 構造函數中的工做隊列和拒絕策略分別有哪些呢?」
你:「emmm...「
(此時你的心理活動:QAQ...不面了,把勞資簡歷還我!) java

一、線程的定義

概念:線程是進程中執行運算的最小單位,是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。面試

二、線程6種狀態及切換

Java中線程的狀態分爲6種,定義在Thread類的State枚舉中。安全

public class Thread implements Runnable {
    ...
    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    ...
}

NEW:初始狀態,建立一個線程對象時就是該狀態。
RUNNABLE:運行狀態,它包含了就緒(READY)和運行中(RUNNING)兩種狀態。當線程對象建立後,調用該對象的 start() 方法就會進入就緒狀態(READY)。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取 CPU 的使用權,在得到 CPU 時間片後會變爲運行中狀態(RUNNING)。
BLOCKED:阻塞狀態,表示線程此時阻塞於鎖。
WAITING:等待狀態,進入該狀態的線程須要等待其餘線程作出一些特定動做(通知或中斷)。
TIMED_WAITING:超時等待狀態,該狀態與 WAITING 的不一樣點在於它能夠在指定的時間後自行返回。
TERMINATED:終止狀態,表示該線程已經執行完。多線程

注意下圖狀態之間的切換。
線程六種狀態切換架構

三、線程的四種建立方式

3.一、實現 Runnable 接口

Runnable接口源碼以下,它只有一個run()方法。併發

public interface Runnable {
    public abstract void run();
}

示例:ide

public class ThreadDemo implements Runnable {

    @Override
    public void run() {
        System.out.println("經過實現 Runnable 接口建立線程");
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
    }

}

3.二、繼承 Thread 類

示例:函數

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("經過繼承 Thread 類建立線程");
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
    }

}

3.三、經過 Callable、Future

經過實現 Runnable 接口與繼承 Thread 類的方式建立的線程是沒有返回值的,然而在有些狀況下,每每須要經過某個線程計算獲得的結果供給另外一個線程使用,這個時候採用Runnable 與 Thread 建立線程並經過自行編寫代碼實現結果返回的方式會不可避免的出現許多錯誤或性能上的問題。基於此問題,JUC併發包(java.util.concurrent)提供瞭解決方案,實現 Callable 的 call() 方法(這個相似Runnable 接口),使用 Future 的 get() 方法進行獲取。
先來看一下Callable接口:性能

public interface Callable<V> {
    V call() throws Exception;
}

建立過程爲:
一、自定義一個類實現Callable接口,重寫call()方法;
二、使用JUC包下的 ExecutorService 生成一個對象,使用 submit() 方法獲得 Future 對象;
三、採用 Future 的 get() 方法獲取返回值。
示例:操作系統

import java.util.concurrent.*;

/**
 * 計算1+2+...+20的結果,開啓三個線程,主線程獲取兩個子線程計算的結果,一個子線程計算1+...+10,一個子線程計算11+...+20。
 */
public class ThreadDemo implements Callable {

    //子線程1,用來計算1+...+10
    @Override
    public Object call() throws Exception {
        int count = 0;
        for (int i = 1; i <= 10; i++)
            count = count + i;
        return count;
    }

    public static void main(String[] args) throws Exception {
        //生成具備兩個線程的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //調用 executorService.submit() 方法獲取 Future 對象
        Future<Integer> result1 = executorService.submit(new ThreadDemo());
        Future<Integer> result2 = executorService.submit(new SubThread());
        //使用 Future的get() 方法等待子線程計算完成返回的結果
        int result = result1.get() + result2.get();
        //關閉線程池
        executorService.shutdown();
        System.out.println(result);
    }

}

class SubThread implements Callable {

    //子線程2,用來計算11+...+20
    @Override
    public Object call() throws Exception {
        int count = 0;
        for (int i = 11; i <= 20; i++)
            count = count + i;
        return count;
    }

}

3.四、經過 JUC 裏面的線程池

Executors 的工廠方法提供了 5 種不一樣的線程池,具體能夠看JDK API,以下圖。
在這裏插入圖片描述
其實在3.三、經過 Callable、Future 方式建立線程的示例就能看到經過Executors.newFixedThreadPool(2) 工廠方法構建線程池。(Executors 的五種工廠方法的區別及優缺點這裏就不說了,下章再細說)

四、幾個常見的線程面試題

4.一、線程與進程的區別

       進程是操做系統進行資源分配的單元,線程是CPU調度運行的單位;一個進程中能夠包含不少線程,線程共享進程的內存等資源;每一個進程擁有各自獨立的一套變量,相互不影響,而線程則共享數據,會存在線程安全問題。

4.二、Thread 的 sleep() 方法和 wait() 方法有什麼區別和共同點?

相同點:

  • 二者均可以暫停線程的執行,都會讓線程進入等待狀態。

不一樣點:

  • sleep() 方法沒有釋放鎖,而 wait() 方法釋放了鎖。
  • sleep() 方法屬於 Thread 類的靜態方法,做用於當前線程;而 wait() 方法是 Object 類的實例方法,做用於對象自己。
  • 執行 sleep() 方法後,能夠經過超時或者調用 interrupt() 方法喚醒休眠中的線程;執行 wait() 方法後,經過調用 notify() 或 notifyAll() 方法喚醒等待線程。

4.三、爲何啓動線程時是調用 start() 方法而不是直接調用 run() 方法?

       new 一個 Thread 時,線程會進入初始狀態;調用 start() 方法時,會啓動一個線程並使線程進入就緒狀態,當分配到時間片後就能夠開始運行了。 start() 會執行線程的相應準備工做,而後自動執行 run() 方法的內容,這是真正的多線程工做。 而直接執行 run() 方法,會把 run() 方法當成一個 main 線程下的普通方法去執行,並不會在某個線程中執行它,因此這並非多線程工做。
  一句話總結就是調用 start() 方法可啓動線程並使線程進入就緒狀態,而 run() 方法只是 Thread 的一個普通方法調用,仍是在主線程裏執行。

五、總結

經過本文但願能對你 Java 線程相關的知識掌握和麪試能有所幫助,下章繼續介紹線程池的相關知識。文中有錯誤的地方,還請留言給予指正,謝謝。喜歡文章的小夥伴們能夠關注點贊收藏哦~

也歡迎你們關注個人公衆號:Java的成神之路,有最新面試資料,必看電子書,架構進階相關資料等。

相關文章
相關標籤/搜索