面試官:這就是你理解的Java多執行緒基礎?

蓉城北斗君發表於2024-05-06

引言

現代的作業系統(Windows,Linux,Mac OS)等都可以同時開啟多個軟體(任務),這些軟體在我們的感知上是同時執行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行程式碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時執行多個任務,如下圖所示,這是因為我們的 CPU 的執行的太快了,把時間分成一段一段的,透過時間片輪轉分給多個任務交替執行。

把CPU的時間切片,分給不同的任務執行,而且執行的非常快,看上去就像在同時執行一樣。例如,網易雲執行50ms,瀏覽器執行50ms,word 執行50ms,人的感官根本感知不到。現在多數的電腦都是多核(多個 CPU )多執行緒,例如4核8執行緒(可以近似的看成8個 CPU ),也是把每個核心執行時間切片分給不同的任務交替執行。

程序與執行緒

程序(Process)是作業系統對一個正在執行的程式的一種抽象,我們可以程序簡單理解為作業系統中正在執行的一個軟體,即把一個任務稱之為一個程序,例如我們的網易雲音樂就是一個程序,瀏覽器又是另外一個程序。

執行緒(Thread)執行緒是一個比程序更小的執行單位,程序是執行緒的容器,一個程序至少有一個執行緒而且可以產生多個執行緒,每個執行緒都執行在程序的上下文中,並共享同樣的程式碼和全域性資料,多執行緒之間比多程序之間更容易共享資料,而且執行緒一般來說都比程序更加高效。


java語言內建了多執行緒支援:JVM 啟動時會建立一個主執行緒,該主執行緒負責執行 main 方法,一個 Java 程式實際上是一個JVM程序,JVM程序用一個主執行緒來執行main()方法內部,我們又可以啟動多個執行緒。此外,JVM還有負責垃圾回收的其他工作執行緒等。

建立執行緒

我們需要區分執行緒和執行緒體兩個概念,執行緒可以驅動任務,因此需要一個描述任務的方式,這個方式就是執行緒體,而我們建立執行緒體有多種方式,而建立執行緒只有一種:將任務(執行緒體)顯示的附著到執行緒上,呼叫 Thread 物件的 start()方法,執行執行緒的初始化操作,然後新執行緒呼叫 run() 方法啟動任務。

建立執行緒體可以使用下面 3 種方式,然而這 3 種方式都是在建立執行緒體,直到呼叫 Thread 物件的 start() 方法時才請求 JVM 建立新的執行緒,具體什麼時候執行有執行緒排程器 Scheduler 決定。

  1. 繼承 Thread 類;
/**
 * 1、定義Thread類的子類
 */
public class MyThread extends Thread {
    //2、重寫Thread類的run方法
    //run()方法體內的內容就是執行緒要執行的程式碼
    @Override
    public void run() {
        // ...
    }
}

public static void main(String[] args) {
    //3、建立執行緒物件
    MyThread mt = new MyThread();
    //4、啟動執行緒
    mt.start();
    /**
     * 呼叫執行緒的start()方法來啟動執行緒,啟動執行緒的實質是請求JVM執行相應的執行緒,
     * 這個執行緒具體什麼時候執行,由執行緒排程器(scheduler)決定
     * 注意:
     *   呼叫start()方法不代表執行緒能立馬執行
     *   執行緒啟動後會執行run()方法
     *   如果啟動了多個執行緒,start()呼叫的順序不一定就是執行緒啟動的順序
     */
}
  1. 實現 Runable 介面;
//1、實現Runnable介面
public class MyRunable implements Runnable{

    //2、實現run方法
    @Override
    public void run() {
        // ...
    }

    public static void main(String[] args) {
        //3、將實現了Runnable介面的物件傳入Thread的構造方法中
        Thread thread = new Thread(new MyRunable());
        //4、啟動執行緒
        thread.start();
    }
}
  1. 實現 callable 介面
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableExample {
    public static void main(String[] args) {
        // 1、實現Callable介面的匿名內部類
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("Callable task is running");
                return 42;
            }
        };
        // 2、將Callable包裝在RunnableFuture實現類中
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 3、將FutureTask例項傳遞給Thread類來執行
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            Integer result = futureTask.get();
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在日常使用中,建議能用介面實現就不要用繼承 Thread 的方式來建立執行緒,原因如下:

  1. 避免單繼承的限制:Java是單繼承的語言,如果一個類繼承Thread類,就無法再繼承其他類。而實現Runnable介面則不會有這種限制,避免了單繼承的侷限性。
  2. 更好的適配性:實現Runnable介面可以更好地支援類似執行緒池的機制,讓執行緒的執行和任務的分離更清晰。傳遞Runnable物件給執行緒池執行任務十分方便,而且可以重複使用。
  3. 更好的物件導向設計:繼承Thread類是一種功能導向的設計,而實現Runnable介面更傾向於物件導向的設計,符合物件導向的程式設計思想。

執行緒的狀態

一個執行緒物件只能呼叫一次 start() 方法啟動新執行緒,並在新執行緒中執行 run() 方法,一旦 run() 方法 執行完畢,執行緒就終止死亡了。我們透過 Thread 類中的列舉類 State 來看一下 Java 執行緒有哪些狀態:

    public enum State {
        
        /**
         * 新建狀態
         * 還沒有執行start()方法的執行緒狀態
         */
        NEW,

        /**
         * 可執行狀態
         * 在Java虛擬機器中執行處於可執行狀態的執行緒,可能正在等待其他資源,例如處理器
         */
        RUNNABLE,
        /**
         * 阻塞狀態
         * 處於阻塞狀態的執行緒正在等待監視器鎖,以進入同步程式碼塊或在呼叫wait()後重新進入
         */
        BLOCKED,
        
        /**
         * 無限期等待狀態
         * 執行緒因呼叫一下方法之一而處於無限期等待狀態:
         * Object.wait with no timeout
         * Thread.join with no timeout
         * LockSupport.park
         * 處於等待狀態的執行緒正在等待另一個執行緒執行特定操作
         */
        WAITING,
    
        /**
         * 具有指定等待時間的等待執行緒的執行緒狀態
         * 執行緒處於定時等待狀態的原因是呼叫了以下方法之一,並指定了正等待時間:
         * Thread.sleep
         * Object.wait with timeout
         * Thread.join with timeout
         * LockSupport.parkNanos
         * LockSupport.parkUntil
         */
        TIMED_WAITING,

        /**
         * 已終止執行緒的執行緒狀態.
         * 執行緒已執行完畢.
         */
        TERMINATED;
    }

由原始碼可知,Java 的執行緒狀態有 6 種:

  1. NEW:新建立的執行緒,還未執行;
  2. RUNNABLE:正在執行中執行緒或正在等待資源分配的準備執行的執行緒;
  3. BLOCKED:等待獲取監視器鎖的執行緒;
  4. WAITING:等待另外一個執行緒執行特定操作,沒有時間限制;
  5. TIMED_WAITING:等待某個特定執行緒在制定時間段內執行特定操作;
  6. TERMINATED:執行緒執行完畢

執行緒狀態的轉換可以參考下圖:


  • NEW 狀態

建立執行緒後未啟動執行緒狀態為 NEW,在該執行緒呼叫 start() 方法以前會一直保持這種狀態。此時,JVM 會為該執行緒分配記憶體並初始化其成員變數的值,但是該執行緒並沒有表現出任何執行緒的動態特徵,程式也不會執行執行緒的執行體,即 run() 方法的部分。

下面的程式碼,我們可以呼叫 Thread.getState() 方法來獲取執行緒的狀態,可以看出列印出來的狀態為 NEW。

public void ThreadTest() {
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("ThreadTest");
        }
    });
    System.out.println(t.getState()); // NEW
}
  • RUNNABLE

當在Java的Thread物件上呼叫start()方法後,以下過程將會發生:

  1. 執行緒狀態變化:執行緒物件的狀態會從NEW(新建)狀態轉變為RUNNABLE(可執行)狀態,表明執行緒已經準備好執行,但尚未分配到CPU執行。
  2. 系統資源分配:執行緒排程器會為該執行緒分配系統資源,例如CPU時間。然而,並不保證立即執行,具體執行時機還取決於執行緒排程器的排程演算法和其他執行中的執行緒。
  3. 執行run()方法:當該執行緒被執行緒排程器選中並分配到CPU時間時,執行緒的run()方法會被呼叫,執行緒開始執行具體的任務邏輯。

處於RUNNABLE 狀態的執行緒要麼正在執行中,要麼已經準備好執行但正在等待系統分配 CPU 資源

在Java虛擬機器(JVM)中,JVM 自帶的執行緒排程器負責決定Java執行緒的執行順序。它會根據執行緒的優先順序和排程演算法來確定哪個執行緒可以獲得 CPU 時間。通常情況下,程式設計師可以透過設定執行緒的優先順序來影響執行緒排程器的決策,但實際執行緒的排程仍由 JVM 負責。

  • BLOCKED

當執行緒嘗試訪問某個由其他執行緒鎖定的程式碼塊時,該執行緒會因為需要等待獲取監視器鎖進入 BLOCKED 狀態,執行緒獲取鎖後就會結束此狀態。

  • WAITING

執行緒正在等待另一個執行緒執行特定操作時處於 WAITING 等待狀態,例如當執行緒呼叫以下方法時會進入 WAITING 等待狀態:

呼叫方法

退出條件

Object.wait()

Object.notify() / Object.notifyAll()

Thread.join()

被呼叫的執行緒(Thread)執行完畢

LockSupport.park()

-

上述方法中的 wait() 和 join() 沒有傳入超時時間 timeout 引數,執行緒只能等待其他執行緒顯示的喚醒或執行完畢,否則不會被分配 CPU 時間片。

  • TIMED_WAITING

執行緒在這種狀態下屬於期限等待,無需其他執行緒顯示的喚醒當前執行緒,在一定時間內被系統自動喚醒。

阻塞和等待的區別在於:阻塞是被動的,等待是主動的。阻塞是在等待獲取鎖,而等待是在等待一定的條件發生。

呼叫方法

退出條件

Thread.sleep()

時間結束

設定了 Timeout 引數的 Object.wait() 方法

時間結束 / Object.notify() / Object.notifyAll()

設定了 Timeout 引數的 Thread.join() 方法

時間結束 / 被呼叫的執行緒執行完畢

LockSupport.parkNanos() 方法

-

LockSupport.parkUntil() 方法

-

  • TERMINATED

執行緒執行完畢或者產生異常而結束會進入 TERMINATED 狀態,進入該狀態的執行緒已經死亡。

執行緒同步

併發問題產生的原因是:多個執行緒同時對一個共享資源進行非原子性操作,這裡麵包含了三個產生併發問題的三個條件:多個執行緒同時,共享資源,非原子性操作,解決執行緒安全問題的本質就是要破壞這三個條件,因此可以把多執行緒的並行執行,修改為單執行緒的序列執行,即同一時刻只讓一個執行緒執行,這種解決方式就叫做互斥鎖。

Java 提供了兩種鎖機制來控制多個執行緒對共享資源的互斥訪問,第一個是 JVM 實現的 synchronized,而另一個是 JDK 實現的 ReentrantLock,從而達到保護共享資源的目的,當多條執行緒執行到被保護的區域時,都需要先去獲取到鎖,這時候只能有一條執行緒獲取到鎖,執行被保護區域的程式碼,其他執行緒在保護區外部等待獲取鎖,直到當前執行緒執行完畢釋放資源後,其他執行緒才有執行的機會。

synchronized 和 ReentrantLock 可以保證可見性、原子性和有序性,另外一個 Java 的關鍵字 Volatile 也可以保證可見性,另外後者還可以禁止指令重排序。

Synchronized

在 Java 中每個物件都可以作為鎖,Synchronized 也是依賴 Java 的物件來實現鎖,一共有三種型別的鎖:

  1. 當前例項鎖:鎖定的是例項物件,即為 this 鎖;
  2. 類物件鎖:鎖定的是類物件,即為 Class 物件鎖;
  3. 物件例項鎖:鎖定的給定的物件例項,即位 Object 鎖;

在使用 Synchronize 時也有三種不同的方式:

  1. 修飾普通方法:使用 this 鎖,在執行該方法前必須先獲取當前例項物件的鎖資源;
  2. 修飾靜態方法:使用 class 鎖,在執行該方法前必須先獲取當前類物件的鎖資源;
  3. 修飾程式碼塊:使用 Object 鎖,在執行該方法前必須先獲取給定物件的鎖資源;
public class A {

    String lockObject = new String();
    
    // 鎖定當前的例項,this鎖,每個例項擁有一個鎖
    public synchronized void a() {};
    // 修飾的是靜態方法,使用的 class 鎖,多個物件共享 class 鎖
    public static synchronized void b() {}
    
    public void c() {
        // 修飾的是程式碼塊,使用的 lockObject 物件的鎖,也是例項鎖
        synchronized(lockObject) {
            // do something 
        }
        // 修飾程式碼塊,使用的 B.class 類物件鎖
        synchronized(B.class) {
            
        }    
    }

}

public class B {
    
}

三種不同的使用方式有不同的應用場景,我們在使用的過程中一定要注意加鎖的物件是誰,否則可能會產生意想不到的結果。在加鎖時,儘量減少加鎖的區域,例如能夠在方法體中對程式碼塊加鎖,就不要在方法上面加鎖,加鎖的區域越短越好。

ReentrantLock

ReentrantLock 是 Java.util.concurrent(J.U.C)包中的鎖,該鎖由 JDK 實現,而 synchronized 是由 JVM 實現的。

public class ReentrantLockDemo{
    private Lock lock = new ReentrantLock();

    private void func() {
        lock.lock(); // 加鎖
        try {
            for (int i = 0; i < 10; i++) {
                system.out.prrint(i)
            }
        } finally {
            lock.unlock(); // 確保釋放鎖
        }
    } 
}

public static void main(Stirng[] args) {
    ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> lockExample.func());
    executorService.execute(() -> lockExample.func());
}

上面的程式碼演示了ReentrantLock 的使用方法,顯示的呼叫 lock()方法加鎖,在 finally 中顯示的釋放鎖。

鎖比較

不同點

synchronized

reentrantLock

實現方式

JVM

JDK

效能

新版本 Java 對 synchronized 進行了大量的最佳化,大致相同

等待可中斷

不可

可以

公平鎖

非公平

預設非公平,支援公平鎖

繫結多個條件

幫點多個 Condition 物件

在需要使用鎖時,除非需要使用 reentrantLock 的高階功能,否則優先使用 synchronized 關鍵字加鎖,這是因為 synchronized 是 JVM 實現的一種鎖機制,JVM 原生支援它,而 ReentrantLock 不是所有的 JDK 版本都支援,並且使用 syschronized 不用擔心沒有釋放鎖而導致死鎖問題,因為 JVM 會確保釋放鎖。

執行緒池

執行緒池可以管理一系列執行緒,當有任務需要處理時,直接從執行緒池裡面獲取執行緒來處理,當執行緒處理完任務時再放回到執行緒池中等待下一個任務,這樣可以減少每次建立執行緒的開銷,提升資源的利用率。執行緒池提供了一種限制和管理資源的方式,每個執行緒池還維護了一些基本的統計資訊,例如 已完成任務的數量等。在《Java 併發程式設計的藝術》一書中提到使用執行緒有三點好處:

  1. 降低資源的消耗率:透過重複利用已建立的執行緒,降低執行緒建立和銷燬造成的開銷;
  2. 提高響應速度:當任務到達時,任務不需要等待執行緒建立結束即可執行;
  3. 提高執行緒的可管理性:執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控;

建立執行緒池

可以使用內建的執行緒池,透過 Executor 框架的工具類 Executors 來建立預先定義好的執行緒池。

Executors

Executors 工具類提供的建立執行緒池的方法如下圖所示:

從上圖中可以看出,Executors 工具類可以建立多種型別的執行緒池,包括:

  1. FixedThreadPool:固定執行緒數量的執行緒池,在建立該執行緒池時,需要傳入一個執行緒池中執行緒個數的 int 引數,當有一個新的任務提交時,執行緒池中若有空閒執行緒,則立即執行。若沒有空閒執行緒,則新的任務會被暫存在一個任務佇列中,待有執行緒空閒時處理。
  2. SingleThreadPool:單執行緒執行緒池,在該執行緒池中只有一個執行緒,若超過一個執行緒提交到該執行緒池,任務會被儲存到任務佇列中,等到該執行緒空閒時,按照先入先出的順序執行佇列中的任務。
  3. CachedThreadPool:可快取執行緒的執行緒池,該執行緒池的執行緒數量不確定,在優先使用空閒執行緒的條件下,遇到新的任務提交時,會建立一個新的執行緒來處理任務,任務處理完畢後回到執行緒池等待複用。
  4. ScheduledExecutorPool:給定的延遲後執行的任務或定期執行任務的執行緒池。

自定義建立

如下圖,可以透過 ThreadPoolExecutor 建構函式來建立執行緒池(推薦)。

優先推薦使用 ThreadPoolExecutor 來建立執行緒池,在《阿里巴巴 Java 開發手冊》中指出執行緒資源必須使用執行緒池來提供,不允許在應用中自行顯示建立執行緒,也強制執行緒池不允許使用 Executors 去建立,而是透過 ThreadPoolExecutor 建構函式的方式來建立執行緒池。

使用內建的執行緒池有以下缺點:

  • newFixedThreadPool 和 SingleThreadPool使用的是無界佇列 LinkedBlockingQueue,任務佇列最大成都為 Integer.MAX_VALUE,可能堆積大量的請求,從而導致 OOM;
  • CachedThreadPool:使用的是同步佇列 SyschronousQueue,允許建立的執行緒數量為 Integer.MAX_VALUE, 如果任務執行較慢,可能會建立大量的執行緒,從而導致 OOM。
  • ScheduledExecutorPool:使用的無界的延遲阻塞佇列 DelayedWorkQueue,任務佇列最大長度為 Integer.MAX_VALUE,可能堆積大量的請求,從而導致 OOM;

實際上內建的執行緒池也是呼叫 ThreadPoolExecutor 來建立的執行緒池:

// 無界佇列 LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

// 無界佇列 LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

// 同步佇列 SynchronousQueue,沒有容量,最大執行緒數是 Integer.MAX_VALUE`
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

// DelayedWorkQueue(延遲阻塞佇列)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

執行緒池引數

我們來看一下自定義建立執行緒池的引數有哪些?

    public ThreadPoolExecutor(int corePoolSize, // 核心執行緒數
                              int maximumPoolSize, // 最大執行緒數
                              long keepAliveTime, // 當執行緒數大於核心執行緒數時,
                                                  // 多餘的空閒執行緒存活時間
                              TimeUnit unit, // 時間單位
                              BlockingQueue<Runnable> workQueue, // 任務佇列
                              ThreadFactory threadFactory, // 執行緒工廠,用於建立執行緒,一般預設
                              RejectedExecutionHandler handler) { // 拒絕策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

如上,ThreadPoolExecutor 中有三個很重要的引數

  1. corePoolSize:任務佇列未達到佇列容量時,最大可以同時執行的執行緒數量。
  2. maxinumPoolSize:任務佇列中存放的任務達到佇列容量時,當前可以同時執行的執行緒數量變為最大執行緒數。
  3. workQueue:新任務來時會先判斷當前執行的執行緒數量是否達到核心執行緒數,若達到核心執行緒數,新任務會被存放在佇列中。

ThreadPoolExecutor 其他常見引數:

  • keepAliveTime:執行緒池中的執行緒數量超過 corePoolSize 時,如果這個時候沒有新的任務提交,核心執行緒以外的執行緒不會立即銷燬,會等到 keepAliveTime 的時間,然後才會銷燬超出部分的執行緒。
  • unit:keepAliveTime 引數的時間單位。
  • threadFactory:executor 建立新執行緒時用到。
  • handler:拒絕策略

下面這張圖可以看出,核心執行緒數量為 4,最大執行緒數量為 8。新任務提交到執行緒池時,首先判斷是否有執行緒或者執行緒數量是否小於核心執行緒數,若滿足則首先建立新的執行緒執行任務,當核心執行緒數到達 corePoolSize 時,將任務快取到任務佇列中,當任務佇列存放的任務到達佇列容量時,再建立新的執行緒,直到達到 maxnumPoolSize 的執行緒數量,後續再根據拒絕策略返回。


拒絕策略

如果當前執行緒池同時執行的執行緒數量達到了最大執行緒數並且佇列也已經放滿了任務時,ThreadPoolExecutor 再接收到新的執行緒時,會執行一些預定義的拒絕策略,例如:

  • ThreadPoolExecutor.AbortPolicy:丟擲 RejectedExecutionException來拒絕新任務的處理。
  • ThreadPoolExecutor.CallerRunsPolicy:呼叫執行自己的執行緒執行任務,也就是直接在呼叫execute方法的執行緒中執行(run)被拒絕的任務,如果執行程式已關閉,則會丟棄該任務。因此這種策略會降低對於新任務提交速度,影響程式的整體效能。如果您的應用程式可以承受此延遲並且你要求任何一個任務請求都要被執行的話,你可以選擇這個策略。
  • ThreadPoolExecutor.DiscardPolicy:不處理新任務,直接丟棄掉。
  • ThreadPoolExecutor.DiscardOldestPolicy:此策略將丟棄最早的未處理的任務請求。

ThreadPoolExecutor 預設執行的是 AbortPolicy,丟擲 RejectedExecutionException 來拒絕新任務。如果不想丟棄任務,也可以使用 CallerRunsPolicy 將任務回退給呼叫者,使用呼叫者執行緒來執行任務。

public static class CallerRunsPolicy implements RejectedExecutionHandler {

        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                // 直接主執行緒執行,而不是執行緒池中的執行緒執行
                r.run();
            }
        }
    }

執行緒池任務處理流程


  1. 當新任務被提交到執行緒池時,首先判斷核心執行緒數量是否達到 corePoolSize,若未達到則建立新的執行緒,直到執行緒池中的執行緒數量到達 corePoolSize 的大小。
  2. 當核心執行緒數量達到 corePoolSize 的數量時,將新達到的任務快取在阻塞佇列中,直到任務佇列容量用完,無法存放新的任務。
  3. 任務佇列無法存放新任務後,若執行緒池中的執行緒數量小於 maxnumPoolSize,則建立新的執行緒來執行任務,直到執行緒數量達到 maxnumPoolSize 的數量。
  4. 根據建立執行緒池時設定的拒絕策略來處理新提交的任務。

後記

本文從程序與執行緒、建立執行緒、執行緒狀態、執行緒同步和執行緒池等多個方面講述了執行緒基礎知識,希望大家對執行緒和執行緒池有了一個基礎的瞭解。後面我們將繼續深入多執行緒的其他知識,例如 Sychronized 的原理分析,JUC 工具類和 ThreadLocal 本地變數等知識,盡請關注。

因本人技術有限,如出現內容錯誤,請評論區糾正。碼字不易,點個關注再走吧~

相關文章