線程池理念分析及其手寫

線程池

  什麼是線程池?

    線程池,thread pool,是一種線程使用模式java

  爲何要使用線程池?

    1:下降資源的消耗,下降線程的建立和銷燬的資源消耗數據庫

    2:提升響應速度,假設線程的建立時間爲T1,執行時間爲T2,銷燬時間爲T3,若是是本身建立線程必然會經歷,這三個時間,那麼若是建立+銷燬>執行,就會有大量時間用在建立和銷燬上,而不是在執行任務上,而線程池關注的就是調整T1和T3的時間,線程池能夠免去T1和T3的時間編程

    3:提升線程的可管理性緩存

  不如先來實現一個本身的線程池

    思想:

      1:爲了優化T1和T3的時間,在使用前,線程必須在池中已經建立好了,而且能夠保持住,既然要保存住,那麼就須要一個容器安全

      2:裏面的線程若是是隻能跑已經在內部寫好的代碼,那麼就沒有意義,因此它必須能接收外部的任務,運行這個任務服務器

      3:外部傳入的任務數量大於線程的執行個數是,多餘的任務如何處理?emmm,用個容器存起來,用阻塞隊列吧,上一章剛寫了網絡

    實現代碼: 

package com.xiangxue.ch6.mypool;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 類說明:本身線程池的實現
 */
public class MyThreadPool2 {
    // 線程池中默認線程的個數爲5
    private static int WORK_NUM = 5;
    
    // 隊列默認任務個數爲100
    private static int TASK_COUNT = 100;

    // 工做線程組
    private WorkThread[] workThreads;

    // 任務隊列,做爲一個緩衝
    private final BlockingQueue<Runnable> taskQueue;
    private final int worker_num;//用戶在構造這個池,但願的啓動的線程數

    // 建立具備默認線程個數的線程池
    public MyThreadPool2() {
        this(WORK_NUM, TASK_COUNT);
    }

    // 建立線程池,worker_num爲線程池中工做線程的個數
    public MyThreadPool2(int worker_num, int taskCount) {
        if (worker_num <= 0) worker_num = WORK_NUM;
        if (taskCount <= 0) taskCount = TASK_COUNT;
        this.worker_num = worker_num;
        taskQueue = new ArrayBlockingQueue<>(taskCount);
        workThreads = new WorkThread[worker_num];
        for (int i = 0; i < worker_num; i++) {
            workThreads[i] = new WorkThread();
            workThreads[i].start();
        }
        Runtime.getRuntime().availableProcessors();
    }


    // 執行任務,其實只是把任務加入任務隊列,何時執行有線程池管理器決定
    public void execute(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    // 銷燬線程池,該方法保證在全部任務都完成的狀況下才銷燬全部線程,不然等待任務完成才銷燬
    public void destroy() {
        // 工做線程中止工做,且置爲null
        System.out.println("ready close pool.....");
        for (int i = 0; i < worker_num; i++) {
            workThreads[i].stopWorker();
            workThreads[i] = null;//help gc
        }
        taskQueue.clear();// 清空任務隊列
    }

    // 覆蓋toString方法,返回線程池信息:工做線程個數和已完成任務個數
    @Override
    public String toString() {
        return "WorkThread number:" + worker_num
                + "  wait task number:" + taskQueue.size();
    }

    /**
     * 內部類,工做線程
     */
    private class WorkThread extends Thread {

        @Override
        public void run() {
            Runnable r = null;
            try {
                while (!isInterrupted()) {
                    r = taskQueue.take();
                    if (r != null) {
                        System.out.println(getId() + " ready exec :" + r);
                        r.run();
                    }
                    r = null;//help gc;
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }

        public void stopWorker() {
            interrupt();
        }

    }
}

 

  JDK中的線程池和工做機制

    線程池的建立

      ThreadPoolExecutor,jdk全部線程池實現的父類併發

      各個參數的意義

        int corePoolSize:線程池核心線程數,池內線程數 < corePoolSize,就會建立新線程, = corePoolSize 就會一直保存這個數量的線程,等之後有任務就會執行,多餘的任務會保存在BlockingQueue中框架

        int maximumPoolSize:線程池容許的最大線程數,若是阻塞隊列也滿了, < maximumPoolSize的時候就會再次建立新的線程,可是不會 > maximumPoolSize 異步

        long keepAliveTime:線程空閒下來的存活時間,這個數值,只有在線程池內的線程數量 > corePoolSize的時候纔會有做用,它決定着 > corePoolSize數量的線程的空閒下來的存活時間

        TimeUnit unit:存活時間單位

        BlockingQueue<Runnable> workQueue:保存任務的阻塞隊列

        ThreadFactory threadFactory:建立線程的工廠,給新建立的線程賦予名字

        RejectedExecutionHandler handler: 線程池的飽和策略,當線程池的最大容許數被沾滿了,阻塞隊列也沾滿了,那麼再放入任務如何處理

          飽和策略:

          AbortPolicy:直接拋出異常

          CallerRunsPolicy:用調用者所在的線程執行任務

          DiscardOldestPolicy:丟棄阻塞隊列中最老的任務,也就是最靠前的任務

          DiscardPolicy:當前任務直接丟棄

          若是這個策略都不是你想要的,能夠本身實現 RejectedExecutionHandler 接口,來完成本身的策略,好比寫數據庫,寫日誌,或者緩存到其餘的裏面,等之後再撿回來

        注意:剛初始化的線程池當中是沒有線程的,只有當往裏面投遞任務纔會建立,若是想一行來就讓裏面的線程數等於corePoolSize能夠調用prestartAllCoreThreads方法

    提交任務

      void execute(Runnable command):用於提交無返回的任務

      Future<T> submit(Callable<T> task):用於提交帶返回值的任務

    關閉線程池

      shutdown(),shutdownNow()

      shutdownNow(),設置線程池的狀態,還會嘗試中止正在運行或者暫停任務的線程

      shutdown()設置線程池狀態,只會中斷全部沒有執行任務的線程

  工做機制

    

 

 

    

    在加入任務的會後會先判斷一下當前線程池中的核心線程數時候小於corePoolSize,若是小於,建立新的線程若是大於嘗試放入阻塞隊列中,若是場入失敗,那麼將嘗試建立新的線程用於運行任務,若是建立新的線程也失敗的話,那麼將執行飽和策略

  合理配置線程池

     根據任務的性質來:計算密集型(CPU),IO密集型,混合型

    計算密集型:

      加密,大數分解,正則.....,線程數適當小一些,

      最大推薦:機器的Cpu核心數+1,爲何要+1,防止頁缺失

       JAVA獲取CPU核心數: 

Runtime.getRuntime().availableProcessors();

    IO密集型:

      讀取文件,數據庫鏈接,網絡通信,線程數適當大一些

      最大推薦:機器的CPU核心數*2

    混合型:

      儘可能拆分,IO密集型遠遠大於計算密集型,拆分意義不大,IO密級型~計算密級型,拆分纔有意義

    注意:隊列的選擇上應該使用有界隊列,無界隊列可能會致使內存溢出,OOM

  預約義的線程池:

    FixedThreadPool:

      建立固定線程數量的,適用於負載較重的服務器,使用了LinkedBlockingQueue做爲阻塞隊列

      

 

    SingleThreadExecutor

      建立單個線程,須要保證順序執行任務,不會有多個線程活動,使用了LinkedBlockingQueue做爲阻塞隊列

 

       

 

     CachedThreadPool

      會根據須要來建立新的線程,適用於執行不少很短時間異步任務的程序,使用了SynchronousQueue做爲阻塞隊列

      

 

     ScheduledThreadPool

      須要按期執行週期任務,Timer不建議使用了。

      newSingleThreadScheduledExecutor:只包含一個線程,只須要單個線程執行週期任務,保證順序的執行各個任務

      newScheduledThreadPool 能夠包含多個線程的,線程執行週期任務,適度控制後臺線程數量的時候

      方法說明:

        schedule:只執行一次,任務還能夠延時執行

        scheduleAtFixedRate:提交固定時間間隔的任務

        scheduleWithFixedDelay:提交固定延時間隔執行的任務

      二者的區別:

        

        scheduleAtFixedRate任務超時:

        規定60s執行一次,有任務執行了80S,下個任務立刻開始執行

        第一個任務 時長 80s,第二個任務20s,第三個任務 50s

        第一個任務第0秒開始,第80S結束;

        第二個任務第80s開始,在第100秒結束;

        第三個任務第120s秒開始,170秒結束

        第四個任務從180s開始

        參加代碼:ScheduleWorkerTime類,執行效果如圖:

        建議在提交給ScheduledThreadPoolExecutor的任務要住catch異常。

   Executor框架圖

    

   Executor框架基本使用流程

     

暫時併發編程就寫到這裏了,後面的併發安全,和JMM模型就先不寫了,打算看看別的了

做者:彼岸舞

時間:2021\01\11

內容關於:併發編程

本文來源於網絡,只作技術分享,一律不負任何責任

相關文章
相關標籤/搜索