java多線程---線程池ThreadPoolExecutor分析

線程池

java中的線程池有很多種,首先來看下其中最基礎的一種線程池ThreadPoolExecutor。線程池的作用爲,減少創建線程和銷燬線程的開銷,對線程進行復用。所以看源碼之前,提出以下問題:

  1. 在線程池中,空閒期,線程怎麼維持?
  2. 在使用時,線程池怎麼創建線程?
  3. 怎麼控制多個線程的併發
  4. 保存任務的載體是什麼。

ThreadPoolExecutor的屬性

關鍵屬性

//ctl這個屬性在後面的方法會經常出現,用法很類似ReentrantReadWriteLock使用一個變量來保存2個值.假設ctl是32位的2進製表示,則高3位,用來表示線程池狀態,後面29位來記錄線程池個數。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

首先來看下ThreadPoolExecutor的幾種狀態值

//該值表示掩碼個數
private static final int COUNT_BITS = Integer.SIZE - 3;
 // 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;
// 所有任務都執行完後,當前線程活動線程數爲0 將要調用terminated方法,
private static final int TIDYING    =  2 << COUNT_BITS;
//終止狀態,terminated方法後的狀態
private static final int TERMINATED =  3 << COUNT_BITS;

在ThreadPoolExecutor後續的方法中,都會根據上訴的各種狀態來進行判斷。

//阻塞隊列
 private final BlockingQueue<Runnable> workQueue;
 //線程池中的獨佔鎖,保證在多線程的情況下的線程安全
 private final ReentrantLock mainLock = new ReentrantLock();
//Worker是具體承載任務的對象 ,可以理解爲把一個thread和runnable組成一個對象,並使用其中的thread去執行。--在下面具體分析。
 private final HashSet<Worker> workers = new HashSet<Worker>();
// 線程池終止的條件隊列
 private final Condition termination = mainLock.newCondition();
 //飽和策略,在ThreadPoolExecutor中有幾個內部類來實現不同策略
 private volatile RejectedExecutionHandler handler;
 //創建線程的工廠
 private volatile ThreadFactory threadFactory;
 
 //核心線程數,也就是當沒有任務需要執行時,會維持corePoolSize個的線程
 private volatile int corePoolSize;
 
 //線程池中的最大線程數
 private volatile int maximumPoolSize;
 
 ...

以上是ThreadPoolExecutor 的幾個核心的點,當然還有好幾個重要的屬性,後面分析。
根據當前的屬性和之前所看過的知識。可以得到以下圖片:
image
也就是說,相當於是一個消費者生產者的模型。由用戶提交任務到線程池中,線程池就開始消費。

拒絕策略如下

public static class AbortPolicy implements RejectedExecutionHandler {
       
        public AbortPolicy() { }

       //當阻塞隊列中的任務滿了,並且線程數也到達最大,則拋出異常
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
//默默拋棄了,不作處理
public static class DiscardPolicy implements RejectedExecutionHandler {
       
        public DiscardPolicy() { }

       
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
//從隊列中拋棄一個任務,把當前的任務加入到隊列中
 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
       
        public DiscardOldestPolicy() { }

       
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
//使用當前調用者的線程,來執行任務
public static class CallerRunsPolicy implements RejectedExecutionHandler {
       
        public CallerRunsPolicy() { }

        
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

構造方法

取其中一個參數最多構造方法

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;
}

可以看到各種屬性都可以自定義。同時Executors還提供了幾種方便的線程池,如下:

//固定大小線程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
}

//線程只有一個
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
}
    
    //阻塞隊列只有一個任務
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

當然在平常操作中,不建議使用這一的方式去創建,不夠靈活。

普通方法

  1. 提交任務
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //獲得ctl值
        int c = ctl.get();
        //判斷現在的線程是否少於核心線程數
        if (workerCountOf(c) < corePoolSize) {
        //少的話就,直接創建新的線程
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //看線程池是否是運行狀態,並且添加任務到阻塞隊列
        if (isRunning(c) && workQueue.offer(command)) {
        //2次檢查,判端是否還是處於運行狀態
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //如果依舊處於運行狀態
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
        //線程池狀態變化了,直接調用拒絕策略
            reject(command);
    }

addWorker 這個方法的內容有一點長,總體可以分爲2步。

  1. 對ctl這個變量進行修改,使線程數+1
  2. 把任務打包成一個woker,並啓動線程
private boolean addWorker(Runnable firstTask, boolean core) {
    //  對狀態值進行修改 ,主要的方式是cas進行修改值
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            //這個判斷可以分爲以下幾種情況會使方法直接返回false
            //1 狀態值大於等於 SHUTDOWN
            //2 狀態值爲shutdown並阻塞隊列爲空
            //3 狀態值爲shutdown 並且傳入的firsttask爲null
            
            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;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                
            }
        }
        //把任務打包成worker放入workers中運行,使用了ThreadPoolExecutor中的獨佔鎖,對整個添加過程加鎖。
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                  //獲取加鎖後的線程池狀態
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                            //加入workers
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //執行本次任務
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

到這裏我們可以看到的是,當線程池的線程少於最大線程時,會創建一個線程取執行任務。而已經到達最大線程數時,會把任務加入阻塞隊列。那麼問題時,阻塞隊列中的任務何時會被調用?下面會分析。

  1. submit
    在線程池中,submit方法與execute方法2者,作用相似,但是submit方法可以返回一個Future的對象,通過對象的get方法可以得到執行的結果。代碼如下:
public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
}

可以看到,在submit中,先吧任務打包成future,然後再調用execute方法去執行。

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
}

通過future的get方法,可以獲得線程的運行結果,同時也可以保證線程的安全。

  1. Worker 內部類
    用戶線程把任務提交到線程後,由worker來執行。
//Worker 的信息,繼承了aqs。前面 講到aqs是鎖的底層實現
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
        
// ---------------

 Worker(Runnable firstTask) {
    setState(-1); // 這裏設置爲-1,可以避免在調用runworker方法之前,被中斷
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);//創建一個線程
}

runworker 也就是去運行線程的方法

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 把state設置爲0,允許中斷
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();//在這裏加鎖的原因是,爲了防止,在任務運行期間,其他有線程調用了shutdown。中斷任務
               //判斷當前的執行狀態是否合理
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //執行任務
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    //統計當前worker完成了多少任務
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //清理worker
            processWorkerExit(w, completedAbruptly);
        }
    }

在上訴代碼中可以看到循環的條件while (task != null || (task = getTask()) != null) 當任務隊列中,不存在任務時,纔會退出循環。這點與消費者生產者的數據模型,非常相似。也是說明了,當有任務存在時,會一直進行循環,觸發任務。

processWorkerExit 清理工作

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) 
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        //統計到整個線程池完成任務的個數
            completedTaskCount += w.completedTasks;
        //從workers中移除當前的worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        //嘗試設置終止狀態,如下2中情況,會變成終止狀態
        //1.shutdown並且工作隊列爲空
        //2、stop,並且沒有活動線程存在
        tryTerminate();
        
        //把線程個數維持在coresize
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

以上代碼可以得出結論,線程池中線程個數,是按worker的個數來計算的。

shutdown 方法,調用以後線程池不會再接受新的任務,但是工作隊列中的任務還是要執行的。

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        //權限檢查
            checkShutdownAccess();
            //設置當前線程池狀態爲shutdown,如果已經是shutdown則直接返回
            advanceRunState(SHUTDOWN);
            //設置中斷標記
            interruptIdleWorkers();
            onShutdown(); //  ScheduledThreadPoolExecutor 的鉤子,下一篇會分析ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        //嘗試吧線程池狀態改爲終止
        tryTerminate();
}

interruptIdleWorkers

//入參可以選擇是否只對一個wokrer中斷
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //獲取workre的鎖
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                    //設置中斷
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

上訴方法中,可以看到,設置中斷之前,需要獲得worker裏面的鎖,由於在上面的runworkre中可以看到,在執行任務期間會獲取鎖,所以這裏不會中斷,正在執行任務的線程。

shutdownNow 操作是更近一步的操作,直接丟棄工作隊列中的任務,並當前執行中的任務也會被中斷。返回值爲被丟棄的任務

 public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            設置中斷標識
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

很明顯,這裏的中斷線程,並沒有獲取當前worker的鎖,而是直接中斷。

總結

線程池就是這樣的一個類,採用了一個原子變量來記錄狀態和線程個數。3位+29位這樣的一個組合。同時利用wokre來包裝線程,處理多個任務,減少的線程的創建和開銷。當然這只是一部分的線程池內容,後續在scheduledThreadPoolExecutor,分析另一部分的內容。

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