Java線程狀態、線程start方法源碼、Java線程池、如何中止一個線程

下面將依次介紹:java

1. 線程狀態緩存

2. start方法源碼app

3. 什麼是線程池?ide

4. 線程池的工做原理和使用線程池的好處工具

5. ThreadPoolExecutor中的Workerpost

6. 線程池工具類如Executors等this

7. 如何中止一個線程(含代碼)spa

8. 如何合理的配置 Java 線程池?如 CPU 密集型的任務,基本線程池 應該配置多大?IO 密集型的任務,基本線程池應該配置多大?用有界隊列好仍是無界隊列好?任務很是多的時候,使用什麼阻塞隊列能獲取 最好的吞吐量?.net

 

 

線程狀態

線程的生命週期線程

首先new一個線程對象,進入New狀態;

調用線程的start方法線程進入Runnable就緒狀態,等待CPU調度;

而後該線程任務被CPU執行時,處於Running狀態,若是放棄了CPU資源(如調用了yield方法),會進入到Runnable狀態,等待CPU調度;

執行過程當中有block IO(如DataInputStream.readUTF()方法)、未獲取到鎖sleepwaitjoin(join底層調用的Object的wait方法)condition.await等操做,進入Blocked狀態;

與之對應的若是線程處於Blocked狀態,block IO 完成、獲取到鎖(也不會當即被CPU執行),sleep時間完、被notify或notifyALL或condition.singal或condition.singalAll喚醒,由Blocked轉爲Runnable狀態,等待CPU執行;

Terminated結束狀態,線程run方法執行完畢、或者異常退出(好比其餘線程調用了該線程的 interrupt方法(該方法僅僅只是設置線程標誌位),若是該線程正在sleep/wait/join會拋出InterruptedException異常,線程終止,見本文末尾如何終止一個線程)

注:Condition能夠由ReentrantLock對象的newCondition生成(底層就是內部類Sync繼承了AQS,sync對象生成的Condition對象),Condition對象與一個ReentrantLock對象相對應,能夠喚醒該ReentrantLock對象對應的Blocked線程。

 

start 方法源碼解析,什麼時候調用的 run() 方法?

  • :首先,會判斷線程的狀態是不是 NEW 狀態,內部對應的狀態標識是個 0,也就是說若是不等於 0,直接拋線程狀態異常(因此不能調用start方法超過一次);
  • :線程在啓動後被加入到 ThreadGroup 中;
  • : start0 是最核心的方法了,就是運行狀態爲 NEW (內部狀態標識爲 0) 的線程;
  • start0 是個 native 方法,也就是 JNI 方法;

看到這裏,你也許會有個疑問,本身重寫的 run 方法是何時被調用的呢?源碼中也沒看到調用啊!!

Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.

上面這段截自 JDK 官方文檔,意思是說:

run 方法是在調用 JNI 方法 start0() 的時候被調用的,被調用後,咱們寫的邏輯代碼才得以被執行。

 

什麼是線程池?線程池的工做原理和使用線程池的好處?

線程池源碼裏最主要的類:ThreadPoolExecutor;

對該類的源碼進行部分解析;

線程池重要的兩個狀態:

  • runState:線程池運行狀態

  • workerCount:工做線程的數量

線程池用一個32位的int來同時保存runState和workerCount,其中高3位(第31到29位)是runState,其他29位workerCount(大約500 million);以下圖

 

 

一個線程池管理了一組工做線程,同時它還包括了一個用於放置等待執行任務的任務隊列(阻塞隊列)

默認狀況下,在建立了線程池後,線程池中的線程數爲 0

當任務提交給 線程池以後的處理策略以下:

1. 若是此時線程池中的數量小於 corePoolSize(核心池的大小),即便線程池中的線程都處於空閒狀態,也要建立新的線程來處理被添加的任務(也 就是每來一個任務,就要建立一個線程來執行任務)。 2. 若是此時線程池中的數量大於等於 corePoolSize,可是緩衝隊列 workQueue 未滿,那麼任務被放入緩衝隊列,則該任務會等待空閒線程將其 取出去執行。

3. 如 果 此 時 線 程 池 中 的 數 量 大 於 等 於 corePoolSize , 緩 衝 隊 列 workQueue 滿,而且線程池中的數量小於 maximumPoolSize線程池 最大線程數),建新的線程來處理被添加的任務。

4. 若是 此時 線程 池中 的數量 大 於 等 於 corePoolSize, 緩 衝 隊列 workQueue 滿,而且線程池中的數量等於 maximumPoolSize,那麼通 過 RejectedExecutionHandler 所指定的策略(任務拒絕策略)來處理此任務;也就是處理任務的優先級爲:核心線程 corePoolSize任務隊列 workQueue最大線程 maximumPoolSize,若是三者都滿了,使用 handler 處理被拒絕的任務。

5. 特別注意,在 corePoolSize 和 maximumPoolSize 之間的線程 數會被自動釋放。當線程池中線程數量大於 corePoolSize 時,若是某線程 空閒時間超過 keepAliveTime,線程將被終止,直至線程池中的線程數目不大於 corePoolSize。這樣,線程池能夠動態的調整池中的線程數。

6. threadFactory

 

建立線程的工廠。全部的線程都是經過這個Factory建立的。

 

默認會使用

 

Executors.defaultThreadFactory 來做線程工廠。

7. handler 線程池的飽和策略。作不了任務了找理由罷工

    • AbortPolicy

      • 直接拋出異常默認策略

    • CallerRunsPolicy

      • 用調用者所在的線程來執行任務;

    • DiscardOldestPolicy

      • 丟棄阻塞隊列中靠最前的任務,並執行當前任務;

    • DiscardPolicy

      • 直接丟棄任務。

 

 

 

 

 

什麼是Workder ?

private final class Worker

extends AbstractQueuedSynchronizer

implements Runnable

 

Worker既實現了Runnable,又繼承了AbstractQueuedSynchronizer(AQS),因此它既是一個可執行的任務,又能夠達到鎖的效果

來源:https://www.toutiao.com/a6698111022613398023/?timestamp=1559695016&app=news_article&group_id=6698111022613398023&tdsourcetag=s_pctim_aiomsg&req_id=2019060508365617201700000107920D2

 

 

 

使用線程池的好處:

1.經過重複利用已建立的線程,減小在建立和銷燬線程上所花的時間以及系統資源的開銷。

2.提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能夠當即執行。

3.提升線程的可管理性。使用線程池能夠對線程進行統一的分配和監控

4.若是不使用線程池,有可能形成系統建立大量線程而致使消耗完系統內存。

 

工具類Executors

 

 

 

對於原理,有幾個接口和類值得咱們關注:

Executor 接口

Executors 類

ExecutorService 接口

AbstractExecutorService 抽象類

ThreadPoolExecutor 類

 

 

Executor 是一個頂層接口,在它裏面只聲明瞭一個方法 execute(Runnable),返回 值爲 void,參數爲 Runnable 類型,從字面意思能夠理解,就是用來執行傳進去的任務的;

而後 ExecutorService 接口繼承了 Executor 接口,並聲明瞭一些方法:submit、 invokeAll、invokeAny 以及 shutDown 等;

抽象類 AbstractExecutorService 實現了 ExecutorService 接口,基本實現了 ExecutorService 中聲明的全部方法;

而後 ThreadPoolExecutor 繼承了類 AbstractExecutorService。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 建立一個支持定時及週期性的任務執行的線程池,多數狀況下可用來替代 Timer 類。

1. newSingleThreadExecutor

建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。 此線程池保證全部任務的執行順序按照任務的提交順序執行。

2.newFixedThreadPool

建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的 最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結 束,那麼線程池會補充一個新線程。

3. newCachedThreadPool

建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60 秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添 加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系 統(或者說 JVM)可以建立的最大線程大小。

 

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

 

 

 在 ThreadPoolExecutor 類中有幾個很是重要的方法:

execute()

submit()

shutdown()

shutdownNow()

execute 和 submit 區別: submit 有返回值,execute 沒有返回值。 因此說能夠根據任務有無返回 值選擇對應的方法。 submit 方便異常的處理。 若是任務可能會拋出異常,並且但願外面的調 用者可以感知這些異常,那麼就須要調用 submit 方法,經過捕獲 Future.get 拋出的異常。

shutdown()和 shutdownNow()的區別: shutdown()和 shutdownNow()是用來關閉線程池的。

shutdown 方法:此方法執行後不得向線程池再提交任務,若是有空閒線程則銷燬空閒線程,等待全部正在執行的任務及位於阻塞隊列中的任務執行結束,而後銷燬全部線程。

shutdownNow 方法:此方法執行後不得向線程池再提交任務,若是有空閒線程則銷燬空閒線程,取消全部位於阻塞隊列中的任務,並將其放入 List容器,做爲返回值;取消正在執行的線程(實際上僅僅是設置正在執行線程的中斷標誌位,調用線程的 interrupt 方法來中斷線程)。

 

如何中止一個線程?

https://blog.csdn.net/zbw18297786698/article/details/53432879

1. 設置標誌位

能夠用一個boolean類型的變量

2.利用Java線程的中斷

和上面的標誌位有些許相似;

線程A被其餘線程調用了interrupt方法設置線程A的標誌位,線程A run方法內部判斷Thread.currentThread().isInterrupted(),若是true就中止,前提是線程A不處於sleep/wait/yield/join;

中斷能夠理解爲線程的一個標誌位屬性,它表示一個運行中的線程是否被其餘線程進行了中斷操做。

中斷比如其餘線程對該線程打了個招呼,其餘線程經過調用該線程的interrupt()方法對其進行中斷操做。

線程經過檢查自身是否被中斷來進行響應,線程經過方法isInterrupted()來進行判斷是否被中斷,也能夠調用靜態方法Thread.interrupted()對當前線程的中斷標識位進行復位。

若是該線程已經處於終結狀態,即便該線程被中斷過,在調用該線程對象的isInterrupted()時依舊會返回false

import java.util.concurrent.TimeUnit;

public class InterruptThreadStateTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Worker(), "WorkerThread");
        t.start();

        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("ThreadName: "+ Thread.currentThread().getName() +" call ThreadName:" + t.getName()+" Method:interrupt");
        t.interrupt();

        TimeUnit.MILLISECONDS.sleep(500);
        System.out.println("ThreadName:"+ t.getName()+" state : " + t.getState());
    }

    private static class Worker implements Runnable{

        @Override
        public void run() {
            Thread t = Thread.currentThread();
            while (true) {
                System.out.println("ThreadName:" + t.getName() + " state : " + t.getState());
                if (t.isInterrupted()) {
                    System.out.println("ThreadName:" + t.getName() + " 被中斷");
                    return;
                }else{
                    System.out.println("ThreadName:" + t.getName() + " 沒有被中斷");
                }
            }

        }
    }
}

run方法執行完畢,線程狀態Terminated

 

 

3. 利用拋出InterruptedException的方式

中斷能夠理解爲線程的一個標誌位屬性,它表示一個運行中的線程是否被其餘線程進行了中斷操做。

中斷比如其餘線程該線程打了個招呼,其餘線程經過調用該線程的interrupt()方法對其進行中斷操做。

線程經過檢查自身是否被中斷來進行響應,線程經過方法isInterrupted()來進行判斷是否被中斷,也能夠調用靜態方法Thread.interrupted()對當前線程的中斷標識位進行復位。若是該線程已經處於終結狀態,即便該線程被中斷過,在調用該線程對象的isInterrupted()時依舊會返回false。

從Java的API中能夠看到,許多聲明拋出InterruptedException的方法(例如Thread.sleep(longmillis)方法,當線程在sleep/wait/yield/join時,若是被中斷,這個異常就會產生)。

這些方法在拋InterruptedException以前,Java虛擬機會先將該線程的中斷標識位清除,而後拋出InterruptedException,此時調用isInterrupted()方法將會返回false。

public class ThreadStopSafeInterrupted { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { // 使用中斷機制,來終止線程
                    if (Thread.currentThread().isInterrupted()) { System.out.println("Interrupted ..."); break; } try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("Interrupted When Sleep ..."); // Thread.sleep()方法因爲中斷拋出異常。 // Java虛擬機會先將該線程的中斷標識位清除,而後拋出InterruptedException, // 由於在發生InterruptedException異常的時候,會清除中斷標記 // 若是不加處理,那麼下一次循環開始的時候,就沒法捕獲這個異常。 // 故在異常處理中,再次設置中斷標記位
 Thread.currentThread().interrupt(); } } } }; // 開啓線程
 thread.start(); Thread.sleep(2000); thread.interrupt(); } }

 

 如何合理的配置 java 線程池?如 CPU 密集型的任務,基本線程池 應該配置多大?IO 密集型的任務,基本線程池應該配置多大?用有界 隊列好仍是無界隊列好?任務很是多的時候,使用什麼阻塞隊列能獲取 最好的吞吐量?

答:

1)配置線程池時,CPU 密集型任務能夠少配置線程數,大概和機器的 cpu 核數相 當,可使得每一個線程都在執行任務。

2)IO 密集型任務則因爲須要等待 IO 操做,線程並非一直在執行任務,則配置盡 可能多的線程,2*cpu 核數

3)有界隊列和無界隊列的配置需區分業務場景,通常狀況下配置有界隊列,在一些 可能會有爆發性增加的狀況下使用無界隊列

4)任務很是多時,使用非阻塞隊列,使用 CAS 操做替代鎖能夠得到好的吞吐量。 synchronousQueue 吞吐率最高。

 

參考來源:

http://ifeve.com/java-threadpool/

https://www.toutiao.com/a6698111022613398023/?timestamp=1559695016&app=news_article&group_id=6698111022613398023&tdsourcetag=s_pctim_aiomsg&req_id=2019060508365617201700000107920D2

http://www.javashuo.com/article/p-rtewvvpu-gz.html

相關文章
相關標籤/搜索