Java線程池

一、線程池

  • 什麼是線程池:

線程池主要是控制運行的線程的數量 ,處理過程中將任務加入隊列 ,然後在線程創建後啓動這些任務,如果線程超過了最大數量,超出的數量的線程排隊等候 ,等其他線程執行完畢,再從隊列中取出任務來執行.

  • 作用:

1)線程複用
2)控制最大併發數
3)管理線程

  • 優點:

1)降低資源消耗
重複利用已創建的線程,降低線程創建和銷燬造成的消耗
2)提高響應速度
任務到達時,不需要創建線程,就能立即執行
3)提高線程的可管理性
線程是稀缺資源,無限創建會消耗系統資源,降低系統穩定性,使用線程池可以統一分配、調優、監控


二、線程池生命週期

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
  • ctl: 表示線程池的運行狀態(runState)和線程池中有效線程的數量(workCount)
    使用了Integer類型來保存,高3位保存runState,低29位保存workerCount
    在這裏插入圖片描述
  • RUNNING: 能接受新提交的任務,也能處理阻塞隊列中的任務
  • SHUTDOWN: 不再接受新提交的任務,可以繼續處理阻塞隊列中已保存的任務
    1)在線程池處於 RUNNING 狀態時,調用 shutdown()方法會使線程池進入到該狀態
    2) finalize() 方法在執行過程中也會調用shutdown()方法進入該狀態
  • STOP:不接受新的任務,也不處理阻塞隊列中的任務,會中斷正在處理任務的線程
    1)在線程池處於 RUNNING 或SHUTDOWN狀態時,調用 shutdownNow()方法會使線程池進入到該狀態
  • TIDYING:當所有的任務都已終止,workCount(有效線程數爲0),線程池進入該狀態後,調用terminated()方法進入TERMINATED狀態
  • TERMINATED:在terminated()方法執行完進入該狀態
    進入TERMINATED狀態條件:
    1)線程池不是RUNNING、TIDYING、TERMINATED狀態
    2)線程池是SHUTDOWN並且workerQueue爲空
    3)workCount爲0
    4)設置TIDYING成功

三、線程池的基本參數

    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;
    }
  • corePoolSize:常駐核心線程數
  • maximumPoolSize:最大同時執行線程數,>=1
  • keepAliveTime:多餘空閒線程存活時間,當線程池的數量超過poolSize,線程空閒時間達到keepAliveTime,空閒的線程會被銷燬,直到剩餘線程數量爲corePoolSize
  • unit:keepAliveTime的單位
  • workQueue:任務隊列,提交但尚未被執行的任務
  • threadFactory:創建新線程的線程工廠,默認即可
  • handler:拒絕策略,當線程隊列滿了,並且工作線程>=線程池最大線程數(corePoolSize>=maximumPoolSize)時,拒絕請求執行的runnable策略

四、實現方式

  • Execuotrs.newFixedThreadPool(int n):定長線程池
   public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }
  • 特點:
    1)定長線程池,控制最大併發數,超出的線程會在隊列中等待
    2)corePoolSize和maximumPoolSize是相等
    3)使用LinkedBlockQueue作爲阻塞隊列
  • Executors.newSingleThreadExecutor():單線程化線程池
   public static ExecutorService newSingleThreadExecutor() {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>()));
   }
  • 特點:
    1)用唯一的工作線程來執行任務
    2)corePoolSize和maximumPoolSize都爲1
    3)使用LinkedBlockQueue作爲阻塞隊列(由鏈表組成的有界阻塞隊列)
  • Executors.newCacheThreadPool():可緩存線程池
   public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
   }
  • 特點:
    1)如果線程池長度超過處理需要,回收空閒線程,如果不足,則創建新線程
    2)corePoolSize爲0和maximumPoolSize爲int的最大值
    3)使用SynchronousQueue作爲阻塞隊列(不存儲元素的阻塞隊列,每個put操作必須等待一個take操作,否則不能添加元素)
    4)來任務了就創建線程,線程空閒超過60s就銷燬線程

五、workQueue

workQueue:等待隊列,當線程池中的線程數量>=corePoolSize時,新提交的任務,會被封裝爲一個Worker對象放入等待隊列,有以下處理方式:

  • LinkedBlockingQueue:無界隊列

線程池中能夠創建的最大線程數就是corePoolSize,maximumPoolSize不會起作用。適用於FixedThreadPool與SingleThreadExcutor。基於鏈表的阻塞隊列,創建的線程數不會超過corePoolSizes(maximumPoolSize值與其一致),當線程池中所有的核心線程都是RUNNING狀態時,這時一個新的任務提交就會放入等待隊列中。
按照FIFO原則對元素進行排序,吞吐量高於ArrayBlockingQueue

  • ArrayListBlockingQueue:有界隊列

使用該方式可以將線程池的最大線程數量限制爲maximumPoolSize,這樣能夠降低資源的消耗(CPU使用率、操作系統資源的消耗、上下文環境切換的開銷),但也使得線程池對線程的調度變得更困難。隊列大小和最大池大小可能需要相互折衷。

  • 設置較大的隊列容量和較小的線程池容量:
    優點:降低資源消耗
    缺點:降低線程處理任務的吞吐量
  • 設置較小的隊列容量和較大的線程池容量:
    優點:CPU使用率會提高
    缺點:當設置的線程池數量較大,同時提交的任務數較多,線程調度變困難,反而可能降低處理任務的吞吐量
  • SynchronousQueue:直接提交策略

適用於CachedThreadPool。它將任務直接提交給線程而不保持它們。如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程


六、拒絕策略
以下內置策略均實現了RejectExecutionHandler接口

  • ThreadPoolExcutor.AbortPolicy():直接拋出異常,默認策略,阻止系統正常運行
  • ThreadPoolExcutor.CallerRunsPolicy():"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是讓調用者所在的線程執行任務
  • ThreadPoolExcutor.DiscardOldersPolicy():拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交
  • ThreadPoolExcutor.DiscardPolicy():不處理,直接丟棄

七、爲什麼要手動創建線程池?
參考阿里巴巴java開發手冊

【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。說明:Executors返回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM
2)CachedThreadPool和ScheduledThreadPool:允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM


八、如何合理配置線程池參數

  • CPU密集型:任務需要大量運算,沒有阻塞
    線程池數量建議爲:CPU核數+1
  • CPU密集型:任務需要大量I/O,頻繁阻塞,大量線程被阻塞,所以需要多配置線程數
    線程池數量建議爲:CPU核數/(1-阻塞係數),阻塞係數一般爲0.8~0.9
    或者線程數爲cpu核數*2

九、死鎖編碼及定位分析
jps定位線程id
jstack 線程id,查看死鎖異常信息

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