一文徹底搞懂線程池

概述

創建線程本身開銷大,反覆創建並銷燬,過多的佔用內存。所以有大量線程創建考慮使用線程池。線程池不用反覆創建線程達到線程的複用,更具配置合理利用cpu和內存減少了開銷,性能會得到提高,還能統一管理任務

比如服務器收到大量請求,每個請求都分配線程去處理,對服務器性能考驗就比較大,如果創建5個以上線程考慮使用線程池。

線程複用原理

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //獲取線程池的線程
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        //如過task不等於null,或者獲取的task不爲空
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                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;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

線程數量:

cpu密集型(加密,計算hash等):cpu核心數1到2倍左右
耗時IO型(讀寫數據庫,文件網絡讀寫)一般是cpu核心數多倍充分利用cpu
線程數公式:cpu核心數x(1+平均等待時間/平均工作時間)

線程池創建

線程池創建使用線程工具類創建

//線程池頂層接口
public interface Executor {
 
    void execute(Runnable command);

一般我們用Executor實現接口創建線程池

public interface ExecutorService extends Executor{
//重要方法
 /**
     * 初始化關閉過程,並不會直接關閉,在任務執行完畢後關閉,
     * 不在增加新任務
     */
    void shutdown();

 /**
     * 判斷線程池是否已經關閉的狀態,關閉返回true,否則返回false
     */
    boolean isShutdown();

 /**
     * 判斷線程是否已經完全終止,任務已經執行完畢
     */
    boolean isTerminated();


 /**
     * 等待一定時間線程是否終止,執行完畢返回true,否則返回false
     *
     * @throws InterruptedException 如果期間被打斷
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
}

 /**
     * 停止所有正在執行的任務,停止處理等待的任務,並返回等待執行的任務列    
     * 表。
     *
     * @return 返回一個集合並返回
     */
    List<Runnable> shutdownNow();

}


/**
     * 線程池工具類,用來創建線程池,提供四個靜態方法創建不同的線程池·   
     *返回 ExecutorService實現類也就是線程池對象(ThreadPoolExecutor)
     * 
     */
public class Executors {


}

//案例
   private static ExecutorService executorService = Executors.newFixedThreadPool(1)//提交任務,Task爲一個具體線程類
            executorService.execute(new Task());

ExecutorService實現類(ThreadPoolExecutor)構造方法參數解析

 public ThreadPoolExecutor(int corePoolSize, //核心線程數
                             //最大線程數
                              int maximumPoolSize,
                              //線程保持存活時間,如果線程數多餘核心數   
                              //量而且限制,會回收閒置的線程
                              long keepAliveTime,
                              //時間單位
                              TimeUnit unit,
                              //任務存儲隊列
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                //ThreadFactory是當線程池需要創建新線程底層使用線程工廠
                //創建,Handler是 線程無法接受新的任務的拒絕策略
             Executors.defaultThreadFactory(), defaultHandler);
    }

在這裏插入圖片描述
參數執行流程:
任務進來,核心線程處理,任務數多於核心數,將任務放進隊列中,隊列滿了就創建新的線程但是線程總數小於最大線程數,如果任務多餘最大線程數,就執行拒絕策略,不在接受任務
在這裏插入圖片描述

拒絕策略

  • 線程池已經關閉,提交任務會失敗
  • 任務隊列滿了而且線程數線程數已經到了最大線程數提交任務失敗
    拒絕策略分類
  • AbortPolicy()默認的策略,直接拋出異常
  • DiscardPolicy()直接默默丟棄
  • DiscardOldestPolicy()隊列最老的丟棄
  • CallerRunsPolicy()誰提交線程誰去執行發,比較人性化,給線程池反衝

如果核心線程數等於最大線程數就會創建固定的線程數的線程池,默認線程工廠能解決大部分情況

Executors創建線程的四個靜態方法

更具業務場景選擇合適的線程池


 /**
     * 創建固定大小的線程數量量的線程池,核心數量等於最大數量
     * 都是nThreads,存活時間爲0沒有意義,無界隊列,請求越來越多
     * 就會進入隊列,容易OOM(內存溢出)
     *
     * @param 線程數量
     * @return 返回線程池
     * @throws IllegalArgumentException 參數異常
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


 /**
     *創建一個只有一個線程的線程池,同樣容易OOM
     *
     * @return 返回線程池
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

  /**
     * 核心數爲0,最大線程數爲Integer.MAX_VALUE,無論多少任務過來都會新建
     * 線程去處理,60秒會回收閒置的線程,同樣容易OOM
     *
     * @return 返回線程池
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }



  /**
     * 該線程池可以安排命令在給定延遲後運行或定期執行。同樣容易OOM
     * @param 核心數
     * @return 返回線程池
     * @throws 參數異常
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
 public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        //延遲五秒執行
//        threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
           //延遲一秒秒執行,每隔三秒執行一次
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
    }

正確創建線程池的方式

言外之意就是默認四種創建線程池的方式是不正確的,首先默認的靈活度不夠,難以適應各種業務場景,而且容易OOM,阿里巴巴編程規範明確規定不要用默認的方式創建線程池,而是直接用ThreadPoolExecutor這個實現類

最佳實踐

 ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)
        		poolExecutor.execute(Task);

//自定義線程池

/**
 * 描述:     演示每個任務執行前後放執行邏輯
 */
public class PauseableThreadPool extends ThreadPoolExecutor {

	//聲明一個鎖
    private final ReentrantLock lock = new ReentrantLock();
    //阻塞工具
    private Condition unpaused = lock.newCondition();
    //標誌位
    private boolean isPaused;

    //一系列重寫構造方法
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,
                handler);
    }
     //每次任務執行都會執行這個方法
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
       
        lock.lock();
        try {
            while (isPaused) {
            	//阻塞線程池
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }

    public void resume() {
        lock.lock();
        try {
            isPaused = false;
            //喚醒線程池
            unpaused.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("我被執行");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10000; i++) {
            pauseableThreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableThreadPool.pause();
        System.out.println("線程池被暫停了");
        Thread.sleep(1500);
        pauseableThreadPool.resume();
        System.out.println("線程池被恢復了");

    }
}

線程池狀態

  • RUNNING :接受任務並處理排隊任務
  • SHUTDOWN :不接受新任務,但處理排隊任務
  • STOP:不接受新任務,也不處理任務,突然終止
  • TIDYING:任務都終止,運行terminate終止線程池
  • TERMINATED:terminate()線程池不在啓動
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章