Java高并發(fā) -- 線程池

Java高并發(fā) -- 線程池

主要是學習慕課網(wǎng)實戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記

在使用線程池后,創(chuàng)建線程變成了從線程池里獲得空閑線程,關(guān)閉線程變成了將線程歸壞給線程池。

JDK有一套Executor框架,大概包括Executor、ExecutorService、AbstractExeccutorService、ThreadPoolExecutor、Executors等成員,位于java.util.concurrent包下。它們之間的關(guān)系如下:

Executor是頂層的接口,ExecutorService接口繼承了它,AbstrctExecutorService繼承了ExecutorService,ThreadPoolExecutor繼承了AbstrctExecutorService。如果用<——表示繼承,<--表示實現(xiàn)接口,它們的關(guān)系可表示如下:

Executor(接口) <—— ExecutorService(接口) <-- AbstrctExecutorService(抽象類) <—— ThreadPoolExecutor(類)

Executors是單獨的一個類,可以看成是“線程池工廠”,它有很多靜態(tài)方法,比如:

  • newFixedThreadPool(int nThread)
  • newSingleThreadExecutor()
  • newCachedThreadPool()
  • newSingleThreadScheduledExecutor()
  • newScheduledThreadPool(int corePoolSize)

newFixedThreadPool該方法返回一個固定線程數(shù)的線程池。當有新任務(wù)提交時,如果線程池中有空閑線程就立即執(zhí)行,否則會進入任務(wù)隊列中,等到有空閑線程了才能執(zhí)行。

newSingleThreadExecutor,該方法返回只有一個線程的線程池,處理策略和上面一樣。實際上就是上面的參數(shù)指定為1而已。

newCachedThreadPool該方法返回一個可根據(jù)實際情況調(diào)整線程數(shù)的線程池,任務(wù)提交后,如果有空閑線程可以復用,則優(yōu)先復用。若線程池中的線程全部在工作,而此時有新任務(wù),則會創(chuàng)建新的線程來處理任務(wù),所有線程執(zhí)行完后會將線程歸還給線程池。

newScheduledThreadPool返回一個ScheduledExecutorService對象,可以有計劃地執(zhí)行任務(wù),比如在某個延時之后開始執(zhí)行,或者周期性地執(zhí)行某個任務(wù)。可以指定線程數(shù)量。

newSingleThreadScheduledExecutor實現(xiàn)了和上面一樣的功能,不過線程池的大小為1。

ScheduledExecutorService有三個方法可以有計劃地執(zhí)行任務(wù)。如:

  • schedule(Runnable command, long delay, TimeUnit unit);該方法可以在給定的延時后,執(zhí)行一個任務(wù);
  • scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);該方法以任務(wù)開始執(zhí)行的時間為initialDelay,加上周期period,就是下一個任務(wù)開始執(zhí)行的時間,以此類推,因此這個方法任務(wù)調(diào)度的頻率是一定的;
  • scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);該方法表示每執(zhí)行完一個任務(wù),延遲delay的時間后,開始執(zhí)行下一個任務(wù),initialDelay還是表示任務(wù)開始的初始時延,上一個任務(wù)結(jié)束的時間點與下一個任務(wù)開始的時間點之差是固定的,固定為delay

即使單個任務(wù)的執(zhí)行時間超過調(diào)度周期,scheduleAtFixedRate也不會讓多個任務(wù)堆疊,比如任務(wù)執(zhí)行需要8s,而調(diào)度周期是2s,調(diào)度第二個任務(wù)時,第一個還沒執(zhí)行完,因此為了避免任務(wù)堆疊,此時調(diào)度周期會變成8s;而采用scheduleWithFixedDelay,兩個任務(wù)之間的實際間隔會變成10s,8s的執(zhí)行+2s的delay。

Executors是線程池的工廠類,通過調(diào)用它的靜態(tài)方法如

Executors.newCachedThreadPool();
Executors.newFixedThreadPool(n);

可返回一個線程池。這些靜態(tài)方法統(tǒng)一返回一個ThreadPoolExecutor,只是參數(shù)不同而已。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {}

包括以上幾個參數(shù),其中:

  • corePoolSize:指定了線程池中線程的數(shù)量;
  • maximumPoolSize:線程池中的最大線程數(shù)量;
  • keepAliveTime:當線程池中線程數(shù)量超過corePoolSize時,多余的空閑線程的存活時間;
  • unit:上一個參數(shù)keepAliveTime的單位
  • 任務(wù)隊列,被提交但還未被執(zhí)行額任務(wù)
  • threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認工廠即可。
  • handler:拒絕策略。當任務(wù)太多來不及處理的時候,采用什么方法拒絕任務(wù)。

最重要的是任務(wù)隊列和拒絕策略。

任務(wù)隊列主要有ArrayBlockingQueue有界隊列、LinkedBlockingQueue無界隊列、SynchronousQueue直接提交隊列。

使用ArrayBlockingQueue,當線程池中實際線程數(shù)小于核心線程數(shù)時,直接創(chuàng)建線程執(zhí)行任務(wù);當大于核心線程數(shù)而小于最大線程數(shù)時,提交到任務(wù)隊列中;因為這個隊列是有界的,當隊列滿時,在不大于最大線程的前提下,創(chuàng)建線程執(zhí)行任務(wù);若大于最大線程數(shù),執(zhí)行拒絕策略。

使用LinkedBlockingQueue時,當線程池中實際線程數(shù)小于核心線程數(shù)時,直接創(chuàng)建線程執(zhí)行任務(wù);當大于核心線程數(shù)而小于最大線程數(shù)時,提交到任務(wù)隊列中;因為這個隊列是有無界的,所以之后提交的任務(wù)都會進入任務(wù)隊列中。newFixedThreadPool就采用了無界隊列,同時指定核心線程和最大線程數(shù)一樣。

使用SynchronousQueue時,該隊列沒有容量,對提交任務(wù)的不做保存,直接增加新線程來執(zhí)行任務(wù)。newCachedThreadPool使用的是直接提交隊列,核心線程數(shù)是0,最大線程數(shù)是整型的最大值,keepAliveTime是60s,因此當新任務(wù)提交時,若沒有空閑線程都是新增線程來執(zhí)行任務(wù),不過由于核心線程數(shù)是0,當60s就會回收空閑線程。

當實際線程數(shù)超過maxPoolSize時,該采取什么樣的策略?

  • AbortPolicy:丟棄任務(wù)并拋出異常;
  • CallerRunPolicy:該任務(wù)被線程池拒絕,由調(diào)用execute方法的線程執(zhí)行該任務(wù);
  • DiscardOldestPolicy:丟棄最老的一個,也就是馬上要執(zhí)行的一個任務(wù);
  • DiscardPolicy:默默丟棄被拒絕的任務(wù),體現(xiàn)在代碼中就是什么也不做。

下面看看CallerRunPolicy怎么拒絕的

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

DiscardOldestPolicy是這樣做的

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll(); // 最老的一個請求在隊列頭部
            e.execute(r);
        }
    }

實現(xiàn)一個簡單的線程池

實現(xiàn)一個類似于Executors.newFixedThreadPool(n)的固定大小線程池,當小于corePoolSize時候,優(yōu)先創(chuàng)建線程去執(zhí)行該任務(wù);當超過該值時,將任務(wù)提交到任務(wù)隊列中,然后各個線程從任務(wù)隊列中取任務(wù)來執(zhí)行。

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

public class MyThreadPool {
    private int workerCount;
    private int corePoolSize;
    private BlockingQueue<Runnable> workQueue;
    private Set<Worker> workers;
    private volatile boolean RUNNING = true;
    public MyThreadPool(int corePoolSize) {
        this.corePoolSize = corePoolSize;
        workQueue = new LinkedBlockingQueue<>();
        workers = new HashSet<>();
    }

    public void execute(Runnable r) {
        if (workerCount < corePoolSize) {
            addWorker(r);
        } else {
            try {
                workQueue.put(r);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void addWorker(Runnable r) {
        workerCount++;
        Worker worker = new Worker(r);
        Thread t = worker.thread;
        workers.add(worker);
        t.start();
    }

    class Worker implements Runnable {
        Runnable task;
        Thread thread;

        public Worker(Runnable task) {
            this.task = task;
            this.thread = new Thread(this);
        }

        @Override
        public void run() {
            while (RUNNING) {
                Runnable task = this.task;
                // 執(zhí)行當前的任務(wù),所以把這個任務(wù)置空,以免造成死循環(huán)
                this.task = null;
                if (task != null || (task = getTask()) != null) {
                    task.run();
                }
            }
        }
    }

    private Runnable getTask() {
        Runnable r = null;
        try {
            r = workQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return r;
    }


    public static void main(String[] args) {
        MyThreadPool threadPool = new MyThreadPool(5);
        Runnable r = new Writer();
        for (int i = 0; i < 10; i++) {
            threadPool.execute(r);
        }
    }


}

class Writer implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " ");
    }
}

Worker實現(xiàn)了Runnale,是真正執(zhí)行任務(wù)的類。當線程池中工作線程小于核心線程時候,調(diào)用addWorker直接start線程執(zhí)行它的第一個任務(wù)。否則,將任務(wù)放入任務(wù)隊列中,等線程來執(zhí)行它們。Worker中的run方法是一個死循環(huán),執(zhí)行第一個任務(wù)(addWorker時調(diào)用start方法執(zhí)行的那個任務(wù)),或者通過getTask方法不斷從任務(wù)隊列中取得任務(wù)來執(zhí)行。正是getTask方法實現(xiàn)了線程的復用,即一個線程雖然只能調(diào)用一次start方法,但是后續(xù)的任務(wù)可以在Worker的run方法里直接調(diào)用任務(wù)的run方法得以執(zhí)行。簡單來說就是在Worker的run里調(diào)用任務(wù)的run方法。

任務(wù)全部執(zhí)行完畢后,線程池需要被關(guān)閉,否則程序一直死循環(huán)。上述代碼中并沒有實現(xiàn)shutdown()方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容