Java線程池的理解與使用

簡介

線程的創建需要開闢虛擬機棧、本地方法棧、程序計數器等線程私有的內存空間,在線程銷燬時需要回收這些系統資源。頻繁地創建和銷燬線程會浪費大量的系統資源,增加併發編程風險,而且當服務器負載過大的時候,如何讓新的線程等待或者友好地拒絕服務?這是線程自身無法解決的。所以需要通過線程池協調多個線程,並實現主次線程隔離、定時執行、週期執行等任務

作用

  • 利用線程池管理並複用線程、控制最大併發數等
  • 實現任務線程隊列緩存策略和拒絕機制
  • 實現定時執行、週期執行等與時間相關的功能
  • 隔離線程環境(根據業務獨立配置線程池,避免相互影響)

創建

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:常駐核心線程數。如果等於0,則任務執行完成後沒有任何請求進入時銷燬線程池的線程;如果大於0,即使本地任務執行完成,核心線程也不會被銷燬
  • maximumPoolSize:線程池能夠容納同時執行的最大線程數。必須大於等於1,且必須大於等於corePoolSize
  • keepAliveTime:線程池中的線程空閒時間,當空閒時間達到keepAliveTime的值時,線程會被銷燬,直到只剩下corePoolSize個線程爲止,避免浪費內存和句柄資源。默認情況下,當線程池中的線程數大於corePoolSize時keepAliveTime纔會起作用,但是當java.util.concurrent.ThreadPoolExecutor#allowCoreThreadTimeOut變量設置爲true時,核心線程超時後也會被回收
  • unit:時間單位。keepAliveTime的時間單位通常是java.util.concurrent.TimeUnit#SECONDS
  • workQueue:緩存隊列。當請求的線程數大於corePoolSize時,線程進入BlockingQueue阻塞隊列,BlockingQueue隊列緩存達到上限後,如果還有新任務需要處理,那麼線程池會創建新的線程,最大線程數爲maximumPoolSize
  • threadFactory:線程工廠。用來生產一組相同任務的線程
  • handler:執行拒絕策略的對象。當workQueue的任務緩存區到達上限後,並且活動線程數達到maximumPoolSize時,線程池通過該策略處理請求

拒絕

  • CallerRunsPolicy:提交任務的線程自己執行該任務
  • AbortPolicy:直接拋出異常(throws RejectedExecutionException)
  • DiscardPolicy:直接丟棄任務
  • DiscardOldestPolicy:丟棄最早進入隊列的任務,接收新任務

定製

自定義ThreadFactory:給線程賦予一個有意義的名字

class UserThreadFactory implements ThreadFactory {
    private static String namePrefix;
    private static ThreadGroup threadGroup;
    private static AtomicInteger nextId = new AtomicInteger(1);

    public UserThreadFactory(String whatFeatureOfGroup) {
        namePrefix = "UserThreadFactory's " + whatFeatureOfGroup + "-Worker-";
        final SecurityManager securityManager = System.getSecurityManager();
        threadGroup = Objects.nonNull(securityManager) ? securityManager.getThreadGroup() 
                : Thread.currentThread().getThreadGroup();
    }

    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix + nextId.getAndIncrement();
        return new Thread(threadGroup, task, name, 0);
    }
}

自定義拒絕策略

class UserRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
     	// todo 自定義的策略
        log.warn("Task rejected. Executor Info:{}", executor.toString());        
    }
}

注意

1,爲什麼不推薦使用Executors快速創建線程池?因爲Executors 提供的很多方法默認使用的都是無界的 LinkedBlockingQueue,高負載情境下,無界隊列很容易導致 OOM
2,謹慎使用默認拒絕策略。線程池默認的拒絕策略會直接拋出運行時異常(throw RejectedExecutionException),建議自定義拒絕策略

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