JAVA多線程之——線程池

線程池

線程池顧名思義,就是一個放置線程的池子。就跟數據庫連接池差不多。線程池通過對併發線程的控制,能有效的節省系統資源的浪費,提高系統的性能。
學習線程池,先了解一下線程池的一個基本結構:
這裏寫圖片描述
Executor

public interface Executor {
   void execute(Runnable command);
}

Executor是一個接口,其中只有一個方法,就是execute方法。所以Executor實際就是一個線程的執行者。
這裏就不把子類的所有方法全部列出來,全部學習一遍,大體可以參照API進行學習。這裏主要學習線程池ThreadPoolExecutor方法開始學習,來了解學習線程池的一個運行原理。
ThreadPoolExecutor的一些重要屬性

  //ctl用來表示線程池的運行狀態,和線程池中任務的數量。
  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //進制位。最大整數轉換成二進制的位數
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //線程池的最大容量2的29次方減1
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    。
    //運行狀態,能接收任務,並對已經添加的任務進行處理
    private static final int RUNNING    = -1 << COUNT_BITS;
    //不能接收新任務,但能處理已經添加的任務
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //不能接收新任務,也不能處理已經添加的任務,並且中斷已經在處理的任務
    private static final int STOP       =  1 << COUNT_BITS;
    //當所有的任務已終止,ctl記錄的"任務數量"爲0,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。
    private static final int TIDYING    =  2 << COUNT_BITS;
    //線程池徹底終止,就變成TERMINATED狀態。
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    //返回線程池的狀態
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //返回線程池的有效容量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //初始化ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    //線程的阻塞隊列
    private final BlockingQueue<Runnable> workQueue;
    //互斥鎖
    private final ReentrantLock mainLock = new ReentrantLock();
    //線程工作集
    private final HashSet<Worker> workers = new HashSet<Worker>();
    //終止條件
    private final Condition termination = mainLock.newCondition();
   // 線程池中線程數量曾經達到過的最大值。
    private int largestPoolSize;
    // 已完成任務數量
    private long completedTaskCount;
    //線程工廠
    private volatile ThreadFactory threadFactory;
    //線程被拒絕時的處理策略
    private volatile RejectedExecutionHandler handler;
    // 保持線程存活時間。
    private volatile long keepAliveTime;
    //是否允許"線程在空閒狀態時,仍然能夠存活
    private volatile boolean allowCoreThreadTimeOut;
    //核心線程池大小
    private volatile int corePoolSize;
    //線程池的最大容量
    private volatile int maximumPoolSize;

這麼多變量,可能要記住也不是很容易。通過分析Execute方法可以更加好的理解工作原理和這些屬性的意義

 public void execute(Runnable command) {
         //執行的線程爲空拋出空指針異常。
        if (command == null)
            throw new NullPointerException();
         //這裏整體可以分三步。
         //1. 如果當前線程池中任務數量(一個任務可以理解就是一個線程)小於中心池的容量(corePoolSize),就直接爲command新建一個線程,加入任務集workers,並啓動該線程。

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
         //2.如果當前任務數量大於了corePoolSize,並且線程池是可運行狀態。就把任務加入到任務隊列workQueue中。 加入隊列之後,再次確認線程池的狀態,這個時候狀態不是可運行的,那就把任務從隊列中刪除,並嘗試終止線程池。如果是可運行,那麼就檢查線程池中工作數量是否爲0,如果沒有了,那麼就添加一個任務爲空的線程。
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //3.如果任務數量大於中心池數量,添加對了也失敗了(這裏隊列是BlockingQueue,前面學習過隊列有有界隊列和無界隊列,所以有可能隊列滿了導致添加失敗。活着其它原因),那麼就再進行一次嘗試添加到任務集中去,如果失敗,執行拒絕策略。
        else if (!addWorker(command, false))
            reject(command);
    }

通過上面的步驟學習,可以大致的理一下思路,線程池,有一箇中心池容量,這個容量沒有滿,就可以直接添加任務運行,而任務是被 添加到一個HashSet的Worker中。如果滿了,就把任務添加到一個BlockingQueue隊列中。都失敗了,就直接運行一個拒絕策略。所以,就要理解三個東西:

  1. 工作集。
  2. 任務隊列
  3. 拒絕策略。
    理解了這三個東西,那麼大致就可以瞭解線程池的一個基本原理。
    工作集 Worker
//Worker是線程池的一個內部類 集成了AQS,實現了Runnable接口。
private final class Worker
       extends AbstractQueuedSynchronizer
       implements Runnable
   {

       private static final long serialVersionUID = 6138294804551838833L;

       final Thread thread;

       Runnable firstTask;

       volatile long completedTasks;

       Worker(Runnable firstTask) {
           setState(-1); // inhibit interrupts until runWorker 運行前,不準中斷
           this.firstTask = firstTask; //初始任務值
           this.thread = getThreadFactory().newThread(this);//通過線程工爲當前任務創建一個線程
       }

       public void run() {
           runWorker(this);//運行
       }
       protected boolean isHeldExclusively() {
           return getState() != 0;
       }

       protected boolean tryAcquire(int unused) {
           if (compareAndSetState(0, 1)) {
               setExclusiveOwnerThread(Thread.currentThread());
               return true;
           }
           return false;
       }

       protected boolean tryRelease(int unused) {
           setExclusiveOwnerThread(null);
           setState(0);
           return true;
       }

       public void lock()        { acquire(1); }
       public boolean tryLock()  { return tryAcquire(1); }
       public void unlock()      { release(1); }
       public boolean isLocked() { return isHeldExclusively(); }

       void interruptIfStarted() {
           Thread t;
           if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
               try {
                   t.interrupt();
               } catch (SecurityException ignore) {
               }
           }
       }
   }

工作集的就是把任務通過線程工廠創建一個該任務的線程並運行。
* 任務隊列*
從定義上看我們知道任務隊列是一個BlockingQueue。所以線程池中的任務隊列可以是任意BlockingQueue的子類。但是常用線程池中常用的的是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue .下一節會學習重用的線程池類型。
拒絕策略
AbortPolicy – 當任務添加到線程池中被拒絕時,它將拋出 RejectedExecutionException 異常。
CallerRunsPolicy – 當任務添加到線程池中被拒絕時,會在線程池當前正在運行的Thread線程池中處理被拒絕的任務。
DiscardOldestPolicy – 當任務添加到線程池中被拒絕時,線程池會放棄等待隊列中最舊的未處理任務,然後將被拒絕的任務添加到等待隊列中。
DiscardPolicy – 當任務添加到線程池中被拒絕時,線程池將丟棄被拒絕的任務。

大致瞭解學習了線程池的一個主要運行過程和基本原理。下一節將會學習JDK自帶的幾種線程池,更加進一步學習和理解線程池。

發佈了61 篇原創文章 · 獲贊 23 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章