Java線程池和阻塞隊列

Java提供了自己的線程池。每次只執行指定數量的線程,java.util.concurrent.ThreadPoolExecutor 就是這樣的線程池。

ThreadPoolExecutor

這裏寫圖片描述

參數介紹:

  1. corePoolSize 核心線程數,指保留的線程池大小(不超過maximumPoolSize值時,線程池中最多有corePoolSize 個線程工作)。
  2. maximumPoolSize 指的是線程池的最大大小(線程池中最大有corePoolSize 個線程可運行)。
  3. keepAliveTime 指的是空閒線程結束的超時時間(當一個線程不工作時,過keepAliveTime 長時間將停止該線程)。
  4. unit 是一個枚舉,表示 keepAliveTime 的單位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7個可選值)。
  5. workQueue 表示存放任務的隊列(存放需要被線程池執行的線程隊列)。

handler 拒絕策略(添加任務失敗後如何處理該任務).

  1. 線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會馬上執行它們。
  2. 當調用 execute() 方法添加一個任務時,線程池會做如下判斷:
    a. 如果正在運行的線程數量小於 corePoolSize,那麼馬上創建線程運行這個任務;
    b. 如果正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
    c. 如果這時候隊列滿了,而且正在運行的線程數量小於 maximumPoolSize,那麼還是要創建線程運行這個任務;
    d. 如果隊列滿了,而且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務了”。
  3. 當一個線程完成任務時,它會從隊列中取下一個任務來執行。

這個過程說明,並不是先加入任務就一定會先執行。假設隊列大小爲 4,corePoolSize爲2,maximumPoolSize爲6,那麼當加入15個任務時,執行的順序類似這樣:首先執行任務 1、2,然後任務3~6被放入隊列。這時候隊列滿了,任務7、8、9、10 會被馬上執行,而任務 11~15 則會拋出異常。最終順序是:1、2、7、8、9、10、3、4、5、6。當然這個過程是針對指定大小的ArrayBlockingQueue<Runnable>來說,如果是LinkedBlockingQueue<Runnable>,因爲該隊列無大小限制,所以不存在上述問題。

LinkedBlockingQueue<Runnable>隊列

package TestBlockingQueue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest implements Runnable {

    @Override
    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new LinkedBlockingDeque<Runnable>();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1,
                TimeUnit.DAYS, queue);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Thread(new ThreadPoolTest(), "TestThread"
                    .concat("" + i)));
            int threadSize = queue.size();
            System.out.println("線程隊列大小爲-->" + threadSize);
        }
        executor.shutdown();
    }
}

執行效果圖

這裏寫圖片描述

可見,線程隊列最大爲7,共執行了10個線線程。因爲是從線程池裏運行的線程,所以雖然將線程的名稱設爲”TestThread”.concat(“”+i),但輸出後還是變成了pool-1-thread-x。

ArrayBlockingQueue<Runnable>(5)隊列

package TestBlockingQueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest implements Runnable {

    @Override
    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // BlockingQueue<Runnable> queue = new LinkedBlockingDeque<Runnable>();
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1,
                TimeUnit.DAYS, queue);
        for (int i = 0; i < 12; i++) {
            executor.execute(new Thread(new ThreadPoolTest(), "TestThread"
                    .concat("" + i)));
            int threadSize = queue.size();
            System.out.println("線程隊列大小爲-->" + threadSize);
        }
        executor.shutdown();
    }
}

運行效果圖

這裏寫圖片描述

因爲線程池的總大小爲6,最大運行的爲3,阻塞隊列爲5,所以容納最多其實是11。編號1,2,3的馬上執行,然後4,5,6,7,8進阻塞隊列。9,10,11的立馬運行。12就拋異常了。

關於加線程

沒有執行完,線程隊列一直處於滿的狀態,直到某個線程執行完,隊列有空位,新線程才加進去,沒空位之前一直阻塞(即等待),我能加進去爲止。

那麼線程池的排除策略是什麼樣呢,一般按如下規律執行:
A. 如果運行的線程少於 corePoolSize,則 Executor 始終首選添加新的線程,而不進行排隊。
B. 如果運行的線程等於或多於 corePoolSize,則 Executor 始終首選將請求加入隊列,而不添加新的線程。
C. 如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。

總結:

  1. 線程池可立即運行的最大線程數 即maximumPoolSize 參數。
  2. 線程池能包含的最大線程數 = 可立即運行的最大線程數 + 線程隊列大小 (一部分立即運行,一部分裝隊列裏等待)
  3. 核心線程數可理解爲建議值,即建議使用的線程數,或者依據CPU核數
  4. add,offer,put三種添加線程到隊列的方法只在隊列滿的時候有區別,add爲拋異常,offer返回boolean值,put直到添加成功爲止。
    5.同理remove,poll, take三種移除隊列中線程的方法只在隊列爲空的時候有區別, remove爲拋異常,poll爲返回boolean值, take等待直到有線程可以被移除。

看看下面這張表就清楚了:

- 拋出異常 特殊值 阻塞 超時
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
檢查 element() peek() 不可用 不可用

記在這裏做爲學習的過程,以後偶爾有空翻起來容易。

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