ThreadPoolExecutor execute() 工作原理

自定義線程池:

public class ThreadPoolTest {

    public static void main(String[] args) {
        //創建等待隊列
        BlockingQueue<Runnable> bqueue= new ArrayBlockingQueue<Runnable>(20);
        //創建線程池,池中保存的線程數爲3,允許的最大線程數爲5
        ThreadPoolExecutor pool=new ThreadPoolExecutor(3,5,50, TimeUnit.MILLISECONDS,bqueue);

        Runnable t1=new MyThread();
        Runnable t2=new MyThread();
        Runnable t3=new MyThread();
        Runnable t4=new MyThread();
        Runnable t5=new MyThread();
        Runnable t6=new MyThread();
        Runnable t7=new MyThread();

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);
        //關閉線程池
        pool.shutdown();

    }

}
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"正在執行。。。");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

七個任務是在線程池的三個線程上執行的。說明下用到的ThreadPoolExecuror類的構造方法中各個參數的含義:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
                    TimeUnit unit,BlockingQueue<Runnable> workQueue) {
       
    }
corePoolSize:線程池中所保存的核心線程數,包括空閒線程。
maximumPoolSize:池中允許的最大線程數。
keepAliveTime:線程池中的空閒線程所能持續的最長時間。
unit:持續時間的單位。
workQueue:任務執行前保存任務的隊列,僅保存由execute方法提交的Runnable任務。

線程池execute()工作的邏輯描述如下:

  1. 如果當前運行的線程,少於corePoolSize,則創建一個新的線程來執行任務。
  2. 如果運行的線程等於或多於 corePoolSize,將任務加入 BlockingQueue。
  3. 如果 BlockingQueue 內的任務超過上限,則創建新的線程來處理任務。
  4. 如果創建的線程數是單錢運行的線程超出 maximumPoolSize,任務將被拒絕策略拒絕。

在瞭解 execute() 方法之前,需要了解一下 clt

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  • 線程池的ctl是一個原子的 AtomicInteger。
  • 這個ctl包含兩個參數 :(1) workerCount 激活的線程數; (2) runState 當前線程池的狀態;
  • 它的低29位用於存放當前的線程數, 因此一個線程池在理論上最大的線程數是 536870911; 高 3 位是用於表示當前線程池的狀態, 其中高三位的值和狀態對應如下:
高三位 狀態 描述
111 RUNNING 接受新任務並處理排隊的任務
000 SHUTDOWN 不接受新任務,但處理排隊的任務
001 STOP

不接受新任務,不處理排隊的任務,中斷正在進行的任務

010 TIDYING

所有任務都已終止,workerCount爲零,線程轉換狀態爲TIDYING,將運行terminated()鉤子方法

110 TERMINATED TERMINATED()已執行完成

爲了能夠使用 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; }

 execute()

public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       int c = ctl.get();

       //如果工作線程數小於核心線程數,
       if (workerCountOf(c) < corePoolSize) {
           //執行addWork,提交爲核心線程,提交成功return。提交失敗重新獲取ctl
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }

       //如果工作線程數大於等於核心線程數,則檢查線程池狀態是否是正在運行,且將新線程向阻塞隊列提交。
       if (isRunning(c) && workQueue.offer(command)) {

           //recheck 需要再次檢查,主要目的是判斷加入到阻塞隊裏中的線程是否可以被執行
           int recheck = ctl.get();

           //如果線程池狀態不爲running,將任務從阻塞隊列裏面移除,啓用拒絕策略
           if (! isRunning(recheck) && remove(command))
               reject(command);
           // 如果線程池的工作線程爲零,則調用addWoker提交任務
           else if (workerCountOf(recheck) == 0)
               addWorker(null, false);
       }

       //添加非核心線程失敗,拒絕
       else if (!addWorker(command, false))
           reject(command);
   }

 

 addWorker()

private static final int COUNT_BITS = Integer.SIZE - 3;  //32-3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 2的29次方-1
//線程池最大大小。注意,實際最大值在內部以CAPACITY爲界限。
private volatile int maximumPoolSize;

/**
 * core:是否核心線程
 */
private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
	for (; ; ) {
		int c = ctl.get();  //ctl的值
		int rs = runStateOf(c);//線程池的狀態

		//判斷是否可以添加任務
		if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
			return false;

		for (; ; ) {
			int wc = workerCountOf(c); //工作線程數
			//是否大於最大值,是否大於核心線程數、是否大於最大線程數
			if (wc >= CAPACITY ||
					wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			//CAS增加工作線程數
			if (compareAndIncrementWorkerCount(c))
				break retry;
			c = ctl.get();
			//如果線程池狀態改變,回到開始重新來
			if (runStateOf(c) != rs)
				continue retry;
		}
	}
	boolean workerStarted = false;
	boolean workerAdded = false;
	Worker w = null;
	//上面的邏輯是考慮是否能夠添加線程,如果可以就cas的增加工作線程數量
	//下面正式啓動線程
	try {
		//新建worker
		w = new Worker(firstTask);

		//獲取當前線程
		final Thread t = w.thread;
		if (t != null) {
			//獲取可重入鎖
			final ReentrantLock mainLock = this.mainLock;
			//鎖住
			mainLock.lock();
			try {
				// Recheck while holding lock.
				// Back out on ThreadFactory failure or if
				// shut down before lock acquired.
				int rs = runStateOf(ctl.get());
				// rs < SHUTDOWN ==> 線程處於RUNNING狀態
				// 或者線程處於SHUTDOWN狀態,且firstTask == null(可能是workQueue中仍有未執行完成的任務,創建沒有初始任務的worker線程執行)
				if (rs < SHUTDOWN ||
						(rs == SHUTDOWN && firstTask == null)) {
					// 當前線程已經啓動,拋出異常
					if (t.isAlive()) // precheck that t is startable
						throw new IllegalThreadStateException();
					//workers 是一個 HashSet 必須在 lock的情況下操作。
					workers.add(w);
					int s = workers.size();
					//設置 largeestPoolSize 標記workAdded
					if (s > largestPoolSize)
						largestPoolSize = s;
					workerAdded = true;
				}
			} finally {
				mainLock.unlock();
			}
			//如果添加成功,啓動線程
			if (workerAdded) {
				t.start();
				workerStarted = true;
			}
		}
	} finally {
		//啓動線程失敗,回滾。
		if (!workerStarted)
			addWorkerFailed(w);
	}
	return workerStarted;
}

execute() 中有三處調用了 addWork() 我們逐一分析。

  • 第一次,條件 if (workerCountOf(c) < corePoolSize) 這個很好理解,工作線程數少於核心線程數,提交任務。所以 addWorker(command, true)
  • 第二次,如果 workerCountOf(recheck) == 0 如果worker的數量爲0,那就 addWorker(null,false)。爲什麼這裏是 null ?之前已經把 command 提交到阻塞隊列了 workQueue.offer(command) 。所以提交一個空線程,直接從阻塞隊列裏面取就可以了。
  • 第三次,如果線程池沒有 RUNNING 或者 offer 阻塞隊列失敗,addWorker(command,false),很好理解,對應的就是,阻塞隊列滿了,將任務提交到,非核心線程池。與最大線程池比較。

重新歸納execute()的邏輯應該是:

  1. 如果當前運行的線程,少於corePoolSize,則創建一個新的線程來執行任務。
  2. 如果運行的線程等於或多於 corePoolSize,將任務加入 BlockingQueue。
  3. 如果加入 BlockingQueue 成功,需要二次檢查線程池的狀態如果線程池沒有處於 Running,則從 BlockingQueue 移除任務,啓動拒絕策略。
  4. 如果線程池處於 Running狀態,則檢查工作線程(worker)是否爲0。如果爲0,則創建新的線程來處理任務。如果啓動線程數大於maximumPoolSize,任務將被拒絕策略拒絕。
  5. 如果加入 BlockingQueue 。失敗,則創建新的線程來處理任務。
  6. 如果啓動線程數大於maximumPoolSize,任務將被拒絕策略拒絕。

 參考地址:https://www.xilidou.com/2018/02/09/thread-corepoolsize/

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