Java線程池解析(上)

一.什麼是線程池

二.jdk提供的線程池以及其功能

1.Executor簡介

2.jdk提供的線程池

三.線程池的內部實現

1.workQueue(任務隊列)

2.拒絕策略

3.自定義線程創建ThreadFactory

4.擴展線程池(線程的開始結束,異常處理等~~~)

例子!

正文:

一:

1.簡而言之,使用線程池後創建線程變成了從線程池獲取線程,關閉線程變成了向池子歸還線程。減少創建線程和歸還線程的開銷。

二:

1.jdk提供了一套Executor框架,ThreadPoolExecutor表示一個線程池。Executors類則扮演線程池工廠角色,通過Executors可以取得一個具有特定功能的線程池。從UML圖中亦可知,ThreadPoolExecutor實現了Executor接口,因此通過這個接口,任何Runnable對象都可以被ThreadPoolExecutor線程池調度。

2.Executor框架提供了各種類型的線程池,主要有以下工廠方法。

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)


以上方法返回了具有不同工作特性的線程池,具體說明如下:(可以先看下邊的線程實現在返回來看這些線程池的功能)
⑴. newFixedThreadPool,返回一個固定數量的線程池。當一個新任務提交時,如果有空閒線程,則執行。否則新任務暫存在一個任務隊列中,待有空閒時,便處理在任務隊列中的任務。
⑵. newSingleThreadExecutor,返回一個線程的線程池。當多餘一個新任務提交時,會暫存在一個任務隊列中,待有空閒時,按先入先出的順序處理在任務隊列中的任務。
⑶. newCachedThreadPool,返回一個可根據實際情況調整線程數量的線程池,線程數量不確定,若有空閒,則會有限複用線程。否則創建新線程處理任務。所有線程在當前任務執行完後,將返回線程池待複用。
⑷ newSingleThreadScheduledExecutor,返回一個ScheduledExecutorService對象,線程池大小爲1。ScheduledExecutorService在Executor接口之上擴展了在給定時間執行某任務的功能。如果在某個固定的延時之後執行,或週期性執行某個任務。可以用這個工廠。
⑸newScheduledThreadPool,也返回一個ScheduledExecutorService對象,但該線程可以指定線程數量。

 

固定線程池

示例:

public class ExecutorExample {
    public static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + " to do sth...");
            try {
                Thread.sleep(1000);//以防線程執行的太快,只開啓一個線程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] a ){
        MyTask myTask = new MyTask();

        //創建固定大小線程池
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i =0;i<10;i++){
            es.submit(myTask);
        }
                /**
         * 他不會暴力的關閉,而會等待所有線程執行完後關閉線程
         * 可以簡單的理解爲shutdown只是發送一個關閉信號,
         * 但在shutdown之後,線程就不能再接受其他任務了.
         */
                 es.shutdown();//線程池需要手動關閉,要不主線程結束後和當前線程任務結束也不會退出
        System.out.println("主線程結束");
        /*
        結果:
        主線程結束
        11執行了!
        12執行了!
        15執行了!
        13執行了!
        14執行了!
        12執行了!
        15執行了!
        11執行了!
        13執行了!
        14執行了!

        */

        /*
        //創建動態線程池..有興趣的可以換成這個  看看運行結果
        ExecutorService escache = Executors.newCachedThreadPool();
        for (int i =0;i<12;i++){
            escache.submit(myTask);
        }
        escache.shutdown();*/

    }
}
計劃任務

newScheduledThreadPool返回一個ScheduledExecutorService對象,可以根據實際對線程進行調度。

//在給定的時間delay,對任務進行一次調度
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
//對任務進行週期性調度,任務調度的頻率一定的,它是以上一個任務開始執行時間爲起點,之後的period時間後調度下一次任務。
//任務開始於初始延時initialDelay,之後的第一個任務開始於initialDelay+period
//如果任務的執行時間大於調度時間,那麼任務就會在上一個任務結束後,立即被調用。
//ps:週期性調度任務並不會無限期的執行,當任務拋出異常會結束,所以請做好異常處理
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,//開始後這麼長時間開始執行任務
                                                  long period,
                                                  TimeUnit unit);//時間單位
//對任務進行週期性調度,在上一個任務結束後,再經過delay長的時間進行任務調度。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
//TimeUnit.SECONDS(單位秒)指定時間單位
//                            還有:
//                            MICROSECONDS    微秒   一百萬分之一秒(就是毫秒/1000)
//                            MILLISECONDS    毫秒   千分之一秒    
//                            NANOSECONDS   毫微秒  十億分之一秒(就是微秒/1000)
//                            SECONDS     秒
//                            MINUTES     分鐘
//                            HOURS      小時
//                            DAYS      天


官方解釋如下:

示例
 

public class ScheduledExecutorExample {

    public static void main(String[] a){

        ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
        ses.scheduleAtFixedRate(new Runnable(){
            @Override
            public void run() {
                long s = System.currentTimeMillis();
                try {
                    System.out.println(Thread.currentThread().getId() + " start doSth...");

                    /**
                     * 在這種情況下,輸出結果1
                     * 值得注意的是要是任務時長大於週期的時候,任務並不會出現堆疊的現象,而且等任務結束後馬上執行下次任務



                     */
                    Thread.sleep(new Random().nextInt(10) * 1000);


                    /* 在這種情況下,輸出結果2
                    * 每隔3秒進行一次調度.
                    */
                    //Thread.sleep(1000);

                    long e = System.currentTimeMillis();

                    System.out.println(Thread.currentThread().getId() + " finish..." +( (e -s)/1000) +"s");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1,3, TimeUnit.SECONDS);

        /*
        * 輸出結果1
        *   10 start doSth...
            10 finish...8s
            10 start doSth...
            10 finish...4s
            12 start doSth...
            12 finish...8s
            10 start doSth...
            10 finish...4s
            13 start doSth...
            13 finish...3s
            12 start doSth...
            12 finish...7s
            14 start doSth...
        * */

        /*
        *   輸出結果2
        *
        *   10 start doSth...
            10 finish...1s
            隔3秒
            10 start doSth...
            10 finish...1s
            隔3秒
            12 start doSth...
            12 finish...1s
            隔3秒
            10 start doSth...
            10 finish...1s
            .....
        * */


另個例子自己敲~~
三.核心線程池的內部實現

對於核心的幾個線程池,無論是newFixedThreadPool()、newSingleThreadExecutor()還是newCacheThreadPool方法,雖然看起來創建的線程具有完全不同的功能特點,但其內部均使用了ThreadPoolExecutor實現。

public static ExecutorService newFixedThreadPool(int nThreads) {//固定大小線程池
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {//一個線程的線程池
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));//無界限的任務隊列
    }
public static ExecutorService newCachedThreadPool() {//
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());//該隊列不接收任務,總是去強行讓線程池創建線程執行任務
    }

由以上線程池的實現可以看到,它們都只是ThreadPoolExecutor類的封裝。我們看下ThreadPoolExecutor最重要的構造函數:

public ThreadPoolExecutor(
            //指定了線程池中的線程數量
            int corePoolSize,
            //指定了線程池中的最大線程數量
            int maximumPoolSize,
            //當前線程池數量超過corePoolSize時,多餘的空閒線程的存活時間,即多少時間內會被銷燬。
            long keepAliveTime,
            //keepAliveTime的單位
            TimeUnit unit,
            //任務隊列,被提交但尚未被執行的任務,放到任務隊列裏。//可以自己實現,下文有寫
            BlockingQueue<Runnable> workQueue,
            //線程工廠,用於創建線程,一般用默認的即可(Executors.defaultThreadFactory())
            ThreadFactory threadFactory,
            //拒絕策略,當任務太多來不及處理,如何拒絕任務。//可以自己實現,下文有寫
            RejectedExecutionHandler handler)

workQueue(任務隊列)
只提交但未執行的任務隊列,它是一個BlockingQueue接口的對象,僅用於存放Runnable對象,根據隊列功能分類,在ThreadPoolExecutor的構造函數中可使用以下幾種BlockingQueue。
1. 直接提交的隊列:該功能由synchronousQueue對象提供,synchronousQueue對象是一個特殊的BlockingQueue。synchronousQueue沒有容量,每一個插入操作都要等待一個響應的刪除操作,反之每一個刪除操作都要等待對應的插入操作。如果使用synchronousQueue,提交的任務不會被真實的保存,而總是將新任務提交給線程執行,如果沒有空閒線程,則嘗試創建線程,如果線程數量已經達到了最大值,則執行拒絕策略,因此,使用synchronousQueue隊列,通常要設置很大的maximumPoolSize值,否則很容易執行拒絕策略。
2. 有界的任務隊列:有界任務隊列可以使用ArrayBlockingQueue實現。ArrayBlockingQueue構造函數必須帶有一個容量參數,表示隊列的最大容量。public ArrayBlockingQueue(int capacity)。當使用有界任務隊列時,若有新任務需要執行時,如果線程池的實際線程數量小於corePoolSize,則會優先創建線程。若大於corePoolSize,則會將新任務加入等待隊列。若等待隊列已滿,無法加入,則在總線程數不大於maximumPoolSize的前提下,創建新的線程執行任務。若大於maximumPoolSize,則執行拒絕策略。可見有界隊列僅當在任務隊列裝滿後,纔可能將線程數量提升到corePoolSize以上,換言之,除非系統非常繁忙,否則確保核心線程數維持在corePoolSize。
3. 無界的任務隊列:無界隊列可以通過LinkedBlockingQueue類實現。與有界隊列相比,除非系統資源耗盡,無界隊列的任務隊列不存在任務入隊失敗的情況。若有新任務需要執行時,如果線程池的實際線程數量小於corePoolSize,則會優先創建線程執行。但當系統的線程數量達到corePoolSize後就不再創建了,這裏和有界任務隊列是有明顯區別的。若後續還有新任務加入,而又沒有空閒線程資源,則任務直接進入隊列等待。若任務創建和處理的速度差異很大,無界隊列會保持快速增長,知道耗盡系統內存。
4. 優先任務隊列:帶有優先級別的隊列,它通過PriorityBlokingQueue實現,可以控制任務執行的優先順序。它是一個特殊的無界隊列。無論是ArrayBlockingQueue還是LinkedBlockingQueue實現的隊列,都是按照先進先出的算法處理任務,而PriorityBlokingQueue根據任務自身優先級順序先後執行,在確保系統性能同時,也能很好的質量保證(總是確保高優先級的任務優先執行)。

ThreadPoolExecutor的任務調度邏輯

newFixedThreadPool()方法的實現,它返回一個corePoolSize和maximumPoolSize一樣的,並使用了LinkedBlockingQueue任務隊列(無界隊列)的線程池。當任務提交非常頻繁時,該隊列可能迅速膨脹,從而系統資源耗盡。
newSingleThreadExecutor()返回單線程線程池,是newFixedThreadPool()方法的退化,只是簡單的將線程池數量設置爲1.
newCachedThreadPool()方法返回corePoolSize爲0而maximumPoolSize無窮大的線程池,這意味着沒有任務的時候線程池內沒有現場,而當任務提交時,該線程池使用空閒線程執行任務,若無空閒則將任務加入SynchronousQueue隊列,而SynchronousQueue隊列是直接提交隊列,它總是破事線程池增加新的線程來執行任務。當任務執行完後由於corePoolSize爲0,因此空閒線程在指定時間內(60s)被回收。對於newCachedThreadPool(),如果有大量任務提交,而任務又不那麼快執行時,那麼系統變回開啓等量的線程處理,這樣做法可能會很快耗盡系統的資源,因爲它會增加無窮大數量的線程。
使用自定義線程池時,要根據具體應用的情況,選擇合適的併發隊列作爲任務的緩衝。當線程資源緊張時,不同的併發隊列對系統行爲和性能的影響均不同。

拒絕策略

線程池中的線程已經用完了,無法繼續爲新任務服務,同時,等待隊列也已經排滿了,再也塞不下新任務了。這時候我們就需要拒絕策略機制合理的處理這個問題。
JDK內置的拒絕策略如下:

ps:jdk提供的拒絕策略都是ThreadPoolExecutor(線程池)內部類方式來實現的,所以在用的時候得   

new ThreadPoolExecutor.DiscardPolicy()  這樣來用

1. AbortPolicy : 直接拋出異常,阻止系統正常運行。

源代碼:

  public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

2. CallerRunsPolicy : 只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。

源代碼:

 public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

3. DiscardOldestPolicy : 丟棄最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。

 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

4. DiscardPolicy : 該策略默默地丟棄無法處理的任務,不予任何處理。如果允許任務丟失,這是最好的一種方案。

  public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

以上內置拒絕策略均實現了RejectedExecutionHandler接口,若以上策略仍無法滿足實際需要,完全可以自己擴展RejectedExecutionHandler接口。RejectedExecutionHandler的定義如下。

public interface RejectedExecutionHandler {
    /**
     * @param r 請求執行的任務
     * @param executor 當前線程池
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

自定義線程創建ThreadFactory

自定義線程池
線程池的作用就是爲了線程複用,也就是避免線程頻繁的創建
但是,最開始的線程從何而來,就是ThreadFactory.

ThreadFactory是一個接口,它有一個方法是創建線程
Thread newThread(Runnable r);

自定義線程可以跟蹤線程何時創建,自定義線程名稱/組/優先級信息.
甚至可以設置爲守護線程.總之自定義線程池可以讓我們更加自由的設置線程池中的所有線程狀態.

擴展線程池

我們想監控每個人物的執行開始時間 結束時間等細節,我們可以通過擴展ThreadPoolExecutor擴展線程池.他提供了beforExecute(),afterExecute(),和terminated()三個接口對線程池進行控制.

在執行任務的線程中將調用beforeExecute和afterExecute等方法,在這些方法中還可以添加日誌、計時、監視或者統計信息收集的功能。無論任務是從run中正常返回,還是拋出一個異常而返回,afterExecute都會被調用。如果任務在完成後帶有一個Error,那麼就不會調用afterExecute。如果beforeExecute拋出一個RuntimeException,那麼任務將不被執行,並且afterExecute也不會被調用。

在線程池完成關閉時調用terminated,也就是在所有任務都已經完成並且所有工作者線程也已經關閉後,terminated可以用來釋放Executor在其生命週期裏分配的各種資源,此外還可以執行發送通知、記錄日誌或者手機finalize統計等操作。

 

例子:

/**
 * 重寫線程池,捕獲了線程運行中的錯誤,線程開始結束時執行的方法,線程池結束時執行的
 * @author ms
 *
 */
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {

    public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
            long keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
            RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                threadFactory, handler);
    }
    
    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task,clientTrace(),Thread.currentThread().getName()));
    }
    @Override
    public void execute(Runnable command) {
        super.execute(wrap(command,clientTrace(),Thread.currentThread().getName()));
    }
    @SuppressWarnings("unused")
    private Exception clientTrace(){
        return new Exception("線程池中某個任務報錯");
    }
    /**
     * 爲傳進了的線程增減一層異常處理
     * @param task
     * @param e
     * @param clientThreadName
     * @return
     */
    private Runnable wrap(final Runnable task,Exception e,String clientThreadName){
        return new Runnable() {
            
            @Override
            public void run() {
                try{
                    task.run();
                }catch(Exception e2){
                    e.printStackTrace();
                    e2.printStackTrace();
                    
                }
                
            }
        };
    }
    /**
     * 線程開始前執行,t 將要運行任務的線程,r 將要執行的任務
     */
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println(Thread.currentThread().getName()+"準備運行");
        super.beforeExecute(t, r);
    }
    /**
     * 線程結束後執行,
     */
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println(Thread.currentThread().getName()+"運行結束了!");
        super.afterExecute(r, t);
    }
    /**
     * 線程池退出時執行
     */
    @Override
    protected void terminated() {
        System.out.println("線程池關閉了!");
        super.terminated();
    }
}



public class TraceThreadPoolExecutorDemo implements Runnable{
    int a,b;
    public TraceThreadPoolExecutorDemo(int a,int b){
        this.a=a;
        this.b=b;
    }
    public void run() {
        double i=a/b;
        System.out.println("i---"+i);
    }
    public static void main(String[] args) {
        // 結果1
        ThreadPoolExecutor tp=new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
        // 結果2
        //ExecutorService tp=new TraceThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10),
        //        Executors.defaultThreadFactory(), //jdk 提供的默認線程工廠
                new ThreadFactory() {//自己定義的線程工廠,可以設置線程類型 名字等
                    
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread(r);
                        //t.setName("自己設定");
                        //t.setDaemon(true);全部設成守護線程
                        return t;
                    }
                },
        //        new ThreadPoolExecutor.DiscardPolicy()  //jdk  提供的幾種拒絕策略
                new RejectedExecutionHandler(){ //自己定義拒絕策略
                    //r 爲要丟棄的任務,executor爲當前線程池
                    //當執行下面方法的時候其實已經丟掉r任務了
                    @Override
                    public void rejectedExecution(Runnable r,
                            ThreadPoolExecutor executor) {
                        //executor.getQueue()  //獲取當前線程的任務隊列,
                        //r.run();  直接在調用者線程運行被拋棄的任務
                        //if(!executor.isShutdown())上述操作必須要在線程池未關閉的狀態下
                        System.out.println(r+"任務被丟棄!");    
                    }
            
                }
        );
        for(int i=0;i<5;i++){
            tp.submit(new TraceThreadPoolExecutorDemo(5,i));
        }
        tp.shutdown();
    }
}

結果1:

i---2.0
i---1.0
i---5.0
i---1.0
結果2:

Thread-0準備運行
Thread-2準備運行
Thread-1準備運行
Thread-4準備運行
Thread-3準備運行
java.lang.Exception: 線程池中某個任務報錯
    at com.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:35)
    at com.TraceThreadPoolExecutor.submit(TraceThreadPoolExecutor.java:27)
    at com.TraceThreadPoolExecutorDemo.main(TraceThreadPoolExecutorDemo.java:54)
java.lang.ArithmeticException: / by zero
    at com.TraceThreadPoolExecutorDemo.run(TraceThreadPoolExecutorDemo.java:19)
    at com.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:50)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at com.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:50)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Thread-0運行結束了!
i---2.0
i---1.0
i---5.0
i---1.0
Thread-1運行結束了!
Thread-3運行結束了!
Thread-2運行結束了!
Thread-4運行結束了!
線程池關閉了!

說明:線程池本身是不會處理某個任務(線程)的異常的,如果出現異常基本不提示錯誤(或者你用execute來提交任務會報一點錯,但是隻是告訴你有錯(讀者可以自己試試))直接就給你少打印一個結果,如例子   這種簡單的錯誤就算不打印錯誤 我們也很快就能發現爲啥少一個結果,我們把0作爲了除數,但是結果1中直接就少了一個線程結果,這不是我們想看到的。

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