Java執行緒池核心原理剖析

Java學習錄發表於2019-03-27

在系統開發時,我們經常會遇到“池”的概念。使用池一種以空間換時間的做法,通常在記憶體中事先儲存一系列整裝待命的物件,以供後期供其他物件隨時呼叫。常見的池有:資料庫連線池,socket連線池,執行緒池等。今天我們就來看一下執行緒池的概念。


Executor

JDK為我們提供了一套Executor框架來方便我們來管理和使用執行緒池。
開啟java.util.concurrent.Executors類,我們可以發現JDK為我們提供了那麼多的方法來幫助我們高效快捷的建立執行緒池:

123456複製程式碼
public static ExecutorService newFixedThreadPool(int nThreads);//建立一個固定數目的、可重用的執行緒池public static ExecutorService newSingleThreadExecutor();//建立一個單執行緒化的執行緒public static ExecutorService newCachedThreadPool();//建立一個可快取執行緒池public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);//建立一個支援定時及週期性任務執行的執行緒池public static ScheduledExecutorService newSingleThreadScheduledExecutor() ;//建立一個支援定時及週期性任務執行的執行緒池public static ExecutorService newWorkStealingPool() ;//建立一個擁有多個任務佇列的執行緒池複製程式碼

上方簡單列舉了幾個Executor框架為我們提供的建立執行緒池的方法,這些執行緒池擁有各種各樣的功能,我想當你剛剛開始使用執行緒的時候google如何使用執行緒池的時候大部分文章都是教你如何使用上方的一些方法建立一個執行緒池。但是如果你去檢視他們的原始碼就會發現他們最後構造的時候都呼叫了同一個構造方法。(除了newWorkStealingPool之外,這個我們在下篇文章再討論)

1234567複製程式碼
ThreadPoolExecutor(int corePoolSize,//執行緒池執行緒數量                           int maximumPoolSize,//執行緒中最大的執行緒數量                           long keepAliveTime,//執行緒池執行緒數量超過corePoolSize的空閒執行緒的存活時間                           TimeUnit unit,//keepAliveTime時間單位                           BlockingQueue<Runnable> workQueue,//被提交還沒執行的任務存放在這裡                           ThreadFactory threadFactory,//執行緒工廠                           RejectedExecutionHandler handler)//任務過多時的拒絕策略複製程式碼

上方的4個引數我想你看到了就會明白了,現在我們著重來講一下下面的三個引數。


WorkQueue

引數workQueue是用來存放已提交但還未執行的任務,JDK為我們提供了一下實現:

直接提交佇列SynchronousQueue

12345678910複製程式碼
當新任務過來的時候它是這樣處理的:if(有空閒執行緒){    處理}else{    if(當前執行緒數<maximumPoolSize){        建立新執行緒處理    }else{        執行拒絕策略    }}複製程式碼

因此使用這個佇列時一定要設定很大的maximumPoolSize

有界的任務佇列ArrayBlockingQueue

12345678910111213複製程式碼
if(當前執行緒數<corePoolSize){    建立新執行緒執行}else{    if(任務佇列是否已滿){       if(當前執行緒<maximumPoolSize){          建立新執行緒處理       }else{          執行拒絕策略        }    }else{       放到任務佇列    }}複製程式碼

無界的任務佇列LinkedBlockingDeque

12345678910111213複製程式碼
if(當前執行緒數<corePoolSize){    建立新執行緒執行}else{    放入任務佇列,等待執行,直到系統資源耗盡}優先任務佇列PriorityBlockingQueue根據任務的優先順序將任務存放在任務佇列特定位置if(當前執行緒數<corePoolSize){    建立新執行緒執行}else{    等待執行,直到系統資源耗盡}複製程式碼


執行緒工廠

第六個引數threadFactory是為執行緒池中建立執行緒的,我們使用Executor框架建立的執行緒就是有threadFactory提供的。我們看一下JDK提供的預設的threadFactory:

1234567891011121314151617181920212223242526複製程式碼
static class DefaultThreadFactory implements ThreadFactory {        private static final AtomicInteger poolNumber = new AtomicInteger(1);        private final ThreadGroup group;        private final AtomicInteger threadNumber = new AtomicInteger(1);        private final String namePrefix;        DefaultThreadFactory() {            SecurityManager s = System.getSecurityManager();            group = (s != null) ? s.getThreadGroup() :                                  Thread.currentThread().getThreadGroup();            namePrefix = "pool-" +                          poolNumber.getAndIncrement() +                         "-thread-";        }        public Thread newThread(Runnable r) {            Thread t = new Thread(group, r,                                  namePrefix + threadNumber.getAndIncrement(),                                  0);            if (t.isDaemon())                t.setDaemon(false);            if (t.getPriority() != Thread.NORM_PRIORITY)                t.setPriority(Thread.NORM_PRIORITY);            return t;        }    }複製程式碼

重點關注一下其中的newThread方法,看到這個我想你就明白了為什麼你使用執行緒池建立出來的執行緒列印的時候名字的來源,還有是否是守護執行緒和優先順序等屬性的來源了。


拒絕策略

看到剛剛的幾種任務佇列我們發現當任務過多時是需要指定拒絕策略來進行拒絕呢,那麼JDK又為我們提供了哪些拒絕策略呢。

1234複製程式碼
AbortPolicy直接丟擲異常。CallerRunsPolicy:如果執行緒池未關閉,則在呼叫者執行緒中執行當前任務DiscardOldestPolicy:丟棄即將執行的任務,然後再嘗試提交當前任務DiscardPolicy:丟棄此任務複製程式碼


執行緒池的擴充套件

ThreadPoolExecutor不僅僅能夠建立各種各樣的執行緒來幫助我們實行功能,它還預留了三個介面來供我們進行擴充套件。

在runWorker方法中呼叫執行緒進行執行之前呼叫了beforeExecute方法,執行之後呼叫了afterExecute()方法

123456789101112131415161718192021222324252627282930313233343536373839複製程式碼
final void runWorker(Worker w) {       Thread wt = Thread.currentThread();       Runnable task = w.firstTask;       w.firstTask = null;       w.unlock();        boolean completedAbruptly = true;       try {           while (task != null || (task = getTask()) != null) {               w.lock();               if ((runStateAtLeast(ctl.get(), STOP) ||                    (Thread.interrupted() &&                     runStateAtLeast(ctl.get(), STOP))) &&                   !wt.isInterrupted())                   wt.interrupt();               try {                   beforeExecute(wt, task);//執行緒執行前                   Throwable thrown = null;                   try {                       task.run();                   } catch (RuntimeException x) {                       thrown = x; throw x;                   } catch (Error x) {                       thrown = x; throw x;                   } catch (Throwable x) {                       thrown = x; throw new Error(x);                   } finally {                       afterExecute(task, thrown);//執行緒執行後                   }               } finally {                   task = null;                   w.completedTasks++;                   w.unlock();               }           }           completedAbruptly = false;       } finally {           processWorkerExit(w, completedAbruptly);       }   }複製程式碼

這兩個方法在ThreadPoolExecutor類中是沒有實現的,我們想要監控執行緒執行前後的資料就可以通過繼承ThreadPoolExecutor類來實現這個擴充套件。
另外還有一個terminated()方法是在整個執行緒池退出的時候呼叫的,我們這裡一併擴充套件。

public class ThreadPoolExecutorDemo extends ThreadPoolExecutor {
    //注意這裡因為ThreadPoolExecutor沒有無參的構造,所以還需要重寫一下構造方法。
    //這裡限於篇幅就不貼了
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println(Thread.currentThread().getId()+"執行完成");

    }
    @Override
    protected void terminated() {
        System.out.println("執行緒池退出");
    }
}

//使用這個demo就可以驗證我們擴充套件的結果了。

public class ThreadPoolDemo {
    static class ThreadDemo extends Thread {
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + ":Thread ID is:" + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutorDemo threadPoolExecutorDemo=  new ThreadPoolExecutorDemo(5,5,0,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
        ThreadDemo threadDemo = new ThreadDemo();
        for (int i = 0; i < 20; i++) {
            threadPoolExecutorDemo.submit(threadDemo);
        }
        threadPoolExecutorDemo.shutdown();
    }
}
複製程式碼

本文所有原始碼:github.com/shiyujun/sy…



Java執行緒池核心原理剖析

相關文章