java多線程(6)線程池

池的概念在java中也是常見,還有鏈接池、常量池等,池的做用也是相似的,對於對象、資源的重複利用,減少系統開銷,提高運行效率。java

線程池的主要功能:
1.減小建立和銷燬線程的次數,提高運行性能,尤爲是在大量異步任務時
2.能夠更合理地管理線程,如:線程的運行數量,防止同一時間大量任務運行,致使系統崩潰多線程

demo

先舉個demo,看看使用線程池的區別,線程池:異步

AtomicLong al = new AtomicLong(0l);
            Long s1 = System.currentTimeMillis();
            ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 100000, 100, TimeUnit.SECONDS, new LinkedBlockingDeque(20000));
            for(int i=0;i<20000;i++){
                pool.execute(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            pool.shutdown();
            pool.awaitTermination(1, TimeUnit.HOURS);
            System.out.println("耗時:"+(System.currentTimeMillis()-s1));
            System.out.println("AtomicLong="+al.get());

結果:ide

耗時:224
AtomicLong=20000

非線程池:性能

AtomicLong al = new AtomicLong(0l);            
            Long s1 = System.currentTimeMillis();
            for(int i=0;i<20000;i++){
                Thread t = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.start();
            }
            while(true){
                if(al.get()==20000){
                    System.out.println("耗時:"+(System.currentTimeMillis()-s1));
                    System.out.println("AtomicLong="+al.get());
                    break;
                }
                Thread.sleep(1);
            }

結果:測試

耗時:2972
AtomicLong=20000

從耗時看2者相差了13倍,差距仍是挺大的,可見有大量異步任務時使用線程池可以提高性能。線程

線程池的主要參數

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

線程池的主要參數有這6個:code

corePoolSize:核心池的大小,核心池的線程不會被回收,沒有任務就處於空閒狀態。對象

maximumPoolSize:線程池最大容許的線程數,接口

keepAliveTime:當線程數超過corePoolSize時,但小於等於maximumPoolSize,會生成‘臨時’線程,‘臨時’線程執行完任務不會立刻被回收,若是在keepAliveTime時間內,尚未新任務的話,纔會被回收。

unit:keepAliveTime的單位。

workQueue:當前線程數超過corePoolSize大小時,新任務會處在等待狀態,存在workQueue中。

threadFactory:生成新線程的工廠。

handler:當線程數超過workQueue的上限時,新線程會被拒絕,這個參數就是新線程被拒絕的方式。

其中corePoolSize和maximumPoolSize相對難理解,再詳細說明:
一、池中線程數小於corePoolSize,新任務都不排隊而是直接添加新線程

二、池中線程數大於等於corePoolSize,workQueue未滿,首選將新任務加入workQueue而不是添加新線程

三、池中線程數大於等於corePoolSize,workQueue已滿,可是線程數小於maximumPoolSize,添加新的線程來處理被添加的任務

四、池中線程數大於大於corePoolSize,workQueue已滿,而且線程數大於等於maximumPoolSize,新任務被拒絕,使用handler處理被拒絕的任務

所以maximumPoolSize、corePoolSize不宜設置過大,不然會形成內存、cpu過載的問題,workQueue而儘可能能夠大一些。

Executors

雖然ThreadPoolExecutor使用很方便,可是建議你們使用jdk已經設定的幾種線程池:
Executors.newCachedThreadPool()(無界線程池,能夠進行線程自動回收)、Executors.newFixedThreadPool(int)(固定大小線程池)和Executors.newSingleThreadExecutor()(單個後線程),它們知足大部分的場景需求。

1.newSingleThreadExecutor 單線程線程池
看下它的實現方式:

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

把corePoolSize設爲1,而workQueue大小設爲無限大,所以永遠只有一個線程在執行任務,新任務都在workQueue中等待。

2.newFixedThreadPool 固定大小線程池

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

和newSingleThreadExecutor 有些相似,只不過從單線程變成能夠指定線程數量,workQueue依舊爲無限。
3.newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

newCachedThreadPool全部新任務都會被當即執行,corePoolSize 設置爲0,maximumPoolSize設置爲整數最大值,全部線程超過60秒未被使用,就會被銷燬。

workQueue

當前線程數超過corePoolSize大小時,新任務會處在等待狀態,存在workQueue中。workQueue的實現方式也就是任務的等待策略,分爲3種:有限隊列、無限隊列、直接提交。

談談前2種,jdk使用了LinkedBlockingQueue而非ArrayBlockingQueue,有限隊列的優點在於對資源和效率更定製化的配置,可是對比無限隊列缺點也很明顯:

1).增長開發難度。須要考慮到corePoolSize ,maximumPoolSize,workQueue,3個參數大小,既要使任務高效地執行,又要避免任務超量的問題,對於開發和測試的複雜度提升不少。
2).防止業務突刺。在互聯網應用業務量暴增也是必須考慮的問題,在使用無限隊列時,雖然執行可能慢一點,可是能保證執行到。使用有限隊列,會出現任務拒絕的問題。
綜上考慮,更建議使用無限隊列,尤爲是對於多線程不是很熟悉的朋友們。

拒絕策略

所謂拒絕策略以前也提到過了,任務太多,超過maximumPoolSize了怎麼辦?固然是接不下了,接不下那只有拒絕了。拒絕的時候能夠指定拒絕策略,也就是一段處理程序。

決絕策略的父接口是RejectedExecutionHandler,JDK自己在ThreadPoolExecutor裏給用戶提供了四種拒絕策略,看一下:

一、AbortPolicy

直接拋出一個RejectedExecutionException,這也是JDK默認的拒絕策略

二、CallerRunsPolicy

嘗試直接運行被拒絕的任務,若是線程池已經被關閉了,任務就被丟棄。

三、DiscardOldestPolicy

移除最晚的那個沒有被處理的任務,而後執行被拒絕的任務。一樣,若是線程池已經被關閉了,任務就被丟棄了

四、DiscardPolicy

悄悄地拒絕任務

相關文章
相關標籤/搜索