【Java併發編程】7.去銀行辦理業務還被業務員教了下線程池

在這裏插入圖片描述

5分鐘

老張辛苦打工一年打算把掙到的錢存到銀行,存錢到銀行時可能會發生若干種狀況。java

  1. 老張銀行門口取號後發現有櫃檯營業可是沒人辦理業務直接辦理了。
  2. 老張取號後發現櫃檯都有人在辦理,等待席有空地,去坐着等辦理去了。
  3. 老張取號後發現櫃檯都有人辦理,等待席也人坐滿了,這個時候銀行經理看到老張是老實人本着關愛老實人的態度,新開一個臨時窗口給他辦理了。
  4. 老張取號後發現櫃檯都滿了,等待座位席也滿了,臨時窗口也人滿了。這個時候銀行經理給出了若干解決策略。
  1. 直接告知人太多不給你辦理了。
  2. 看到老張就來氣,也不給不辦理也不讓他走。
  3. 經理讓老張取嘗試跟座位席中最前面的人聊一聊看是否能夠加塞,能夠就辦理,不能夠仍是被踢走。
  4. 經理直接跟老張說誰讓你來的你找誰去我這辦理不了。

在這裏插入圖片描述
上面的這個流程幾乎就跟JDK線程池的大體流程相似,web

  1. 營業中的3個窗口對應核心線程池數:corePoolSize
  2. 銀行總的營業窗口數對應:maximumPoolSize
  3. 打開的臨時窗口在多少時間內無人辦理則關閉對應:unit
  4. 銀行裏的等待座椅就是等待隊列:workQueue
  5. 沒法辦理的時候銀行給出的解決方法對應:RejectedExecutionHandler
  6. threadFactory 該參數在JDK中是 線程工廠,用來建立線程對象,通常不會動。

5分鐘線程池的核心工做流程講解完畢,更細節的知識看下面。數據庫

什麼是線程池

簡單理解就是 預先建立好必定數量的線程對象,存入緩衝池中,須要用的時候直接從緩衝池中取出,用完以後不要銷燬,還回到緩衝池中。編程

想多瞭解點線程池的預備知識可參考此文 blogsegmentfault

線程池存在必要性

  1. 提升線程的利用率,下降資源的消耗。
  2. 提升響應速度,線程的建立時間爲T1,執行時間T2,銷燬時間T3,用線程池能夠免去T1和T3的時間。
  3. 便於統一管理線程對象
  4. 可控制最大併發數

手動實現

若是先不看線程池源碼讓咱們本身手動實現一個線程池你能夠考慮到幾個重要點?數組

  1. 有若干個初始化好的線程數組來充當線程池。
  2. 線程池要去一個 等待的任務隊列 中去拿任務。

簡單來講就是初始化N個線程充當線程池而後一塊兒去阻塞隊列中進行阻塞take,新添加的任務都經過put將任務追加到任務隊列,關於任務隊列的講解看這blog緩存

  1. 核心類
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("準備關閉線程池");
        for (int i = 0; i < worker_num; i++) {
            workThreads[i].stopWorker();
            workThreads[i] = null;//help gc
        }
        taskQueue.clear();// 清空任務隊列
    }

    // 覆蓋toString方法,返回線程池信息:工做線程個數和已完成任務個數
    @Override
    public String toString() {
        return "線程池大小 :" + worker_num  + " 等待執行任務個數:" + 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() + " 準備執行 :" + r);
                        r.run();
                    }
                    r = null; //help gc;
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        public void stopWorker() {
            interrupt();
        }
    }
}
  1. 測試類
public class TestMyThreadPool {
    public static void main(String[] args) throws InterruptedException {
        // 建立3個線程的線程池
        MyThreadPool2 t = new MyThreadPool2(3, 5);
        t.execute(new MyTask("testA"));
        t.execute(new MyTask("testB"));
        t.execute(new MyTask("testC"));
        t.execute(new MyTask("testD"));
        t.execute(new MyTask("testE"));
        System.out.println(t);
        Thread.sleep(10000);
        t.destroy();// 全部線程都執行完成 才destory
        System.out.println(t);
    }
    // 任務類
    static class MyTask implements Runnable {
        private String name;
        private Random r = new Random();
        public MyTask(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }

        @Override
        public void run() {// 執行任務
            try {
                Thread.sleep(r.nextInt(1000) + 2000); //隨機休眠
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getId() + " 被打斷:"
                        + Thread.currentThread().isInterrupted());
            }
            System.out.println("任務 " + name + " 完成");
        }
    }
}

ThreadPoolExecutor

ThreadPoolExecutor是JDK中全部線程池實現類的父類,構造函數有多個入參經過靈活的組合來實現線程池的初始化,核心構造函數以下。安全

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

重要參數解析

  1. corePoolSize

此值是用來初始化線程池中核心線程數,當線程池中線程池數< corePoolSize時,系統默認是添加一個任務才建立一個線程池。能夠經過調用prestartAllCoreThreads方法一次性的啓動corePoolSize個數的線程。當線程數 = corePoolSize時,新任務會追加到workQueue中。網絡

  1. maximumPoolSize

maximumPoolSize表示容許的最大線程數 = (非核心線程數+核心線程數),當BlockingQueue也滿了,但線程池中總線程數 < maximumPoolSize時候就會再次建立新的線程。多線程

  1. keepAliveTime

非核心線程 =(maximumPoolSize - corePoolSize ) ,非核心線程閒置下來不幹活最多存活時間。

  1. unit

線程池中非核心線程保持存活的時間

TimeUnit.DAYS; 天
TimeUnit.HOURS; 小時
TimeUnit.MINUTES; 分鐘
TimeUnit.SECONDS; 秒
TimeUnit.MILLISECONDS; 毫秒
TimeUnit.MICROSECONDS; 微秒
TimeUnit.NANOSECONDS; 納秒

  1. workQueue

線程池 等待隊列,維護着等待執行的Runnable對象。當運行當線程數= corePoolSize時,新的任務會被添加到workQueue中,若是workQueue也滿了則嘗試用非核心線程執行任務,另外等待隊列儘可能用有界的哦!!

  1. threadFactory

建立一個新線程時使用的工廠,能夠用來設定線程名、是否爲daemon線程等等。

  1. handler

corePoolSizeworkQueuemaximumPoolSize都不可用的時候執行的 飽和策略。

AbortPolicy :直接拋出異常,默認用此
CallerRunsPolicy:用調用者所在的線程來執行任務
DiscardOldestPolicy:丟棄阻塞隊列裏最老的任務,隊列裏最靠前的任務
DiscardPolicy :當前任務直接丟棄
想實現本身的飽和策略,實現RejectedExecutionHandler接口便可

形象流程圖以下:
在這裏插入圖片描述

提交

  1. execute
    不須要返回
// 核心思想跟上面的流程圖相似
    public void execute(Runnable command) {
        if (command == null) //規範性檢查
            throw new NullPointerException();
        int c = ctl.get();//當前工做的線程數跟線程狀態 ctl = AtomicInteger CAS級別 
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
            // 若是當前線程池中工做線程數小於核心線程數,直接添加任務 而後return
                return;
            c = ctl.get();// 添加失敗了從新得到線程池中工做線程數
        }
        if (isRunning(c) && workQueue.offer(command)) { 
        // 線程池狀態是否處於可用,可用就嘗試將線程添加到queue
            int recheck = ctl.get();// 得到線程池狀態
            if (! isRunning(recheck) && remove(command))
                reject(command);// 若是線程狀態不在運行中 則remove 該任務
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))// 嘗試將任務用非核心線程執行,
            reject(command);//失敗了則執行失敗策略。
    }
  1. submit
    須要返回值 ThreadPoolExecutor extends AbstractExecutorService父類中存在一個submit方法,
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

關閉線程池

注意線程之間是協做式的哦,因此的關閉只是發出關閉指令。

  1. shutdown()
    將線程池狀態置爲shutdown,並不會當即中止:
  1. 中止接收外部submit的任務
  2. 內部正在跑的任務和隊列裏等待的任務,會執行完
  3. 等到第二步完成後,才真正中止
  1. shutdownNow()
    將線程池狀態置爲stop。企圖當即中止,事實上不必定:
  1. 跟shutdown()同樣,先中止接收外部提交的任務
  2. 忽略隊列裏等待的任務
  3. 嘗試將正在跑的任務interrupt中斷
  4. 返回未執行的任務列表

shutdown 跟shutdownnow簡單來講區別以下:

shutdownNow()能當即中止線程池,正在跑的和正在等待的任務都停下了。這樣作當即生效,可是風險也比較大。shutdown()只是關閉了提交通道,用submit()是無效的;而內部該怎麼跑仍是怎麼跑,跑完再停。

  1. awaitTermination
pool.showdown()
boolean b = pool.awaitTermination(3, TimeUnit.SECONDS)

awaitTermination有兩個參數,一個是timeout即超時時間,另外一個是unit即時間單位。這個方法會使線程等待timeout時長,當超過timeout時間後,會監測ExecutorService是否已經關閉,若關閉則返回true,不然返回false。通常狀況下會和shutdown方法組合使用,調用後當前線程會阻塞,直到

  1. 等全部已提交的任務(包括正在跑的和隊列中等待的)執行完
  2. 或者等超時時間到
  3. 或者線程被中斷,拋出InterruptedException

總結

優雅的關閉,用shutdown()
想立馬關閉,並獲得未執行任務列表,用shutdownNow()
優雅的關閉,發出關閉指令後看下是否真的關閉了用awaitTermination()。

合理配置線程池

線程在Java中屬於稀缺資源,線程池不是越大越好也不是越小越好。任務分爲計算密集型、IO密集型、混合型。

  1. 計算密集型:大部分都在用CPU跟內存,加密,邏輯操做業務處理等。
  2. IO密集型:數據庫連接,網絡通信傳輸等。
  1. 計算密集型通常推薦線程池不要過大,通常是CPU數 + 1,+1是由於可能存在頁缺失(就是可能存在有些數據在硬盤中須要多來一個線程將數據讀入內存)。若是線程池數太大,可能會頻繁的 進行線程上下文切換跟任務調度。得到當前CPU核心數代碼以下:
Runtime.getRuntime().availableProcessors();
  1. IO密集型:線程數適當大一點,機器的Cpu核心數*2。
  2. 混合型:若是密集型站大頭則拆分的必要性不大,若是IO型佔據很多有必要,Mark 下。

常見線程池

每一個線程池都是一個實現了接口ExecutorService而且繼承自ThreadPoolExecutor的具體實現類,這些類的建立統一由一個工廠類Executors來提供對外建立接口。Executors框架圖以下:

在這裏插入圖片描述
ThreadPoolExecutor中一個線程就是一個Worker對象,它與一個線程綁定,當Worker執行完畢就是線程執行完畢。而Worker帶了鎖AQS,根據我後面準備寫的讀寫鎖的例子,發現線程池是線程安全的。看看圖二的類圖。
下面簡單介紹幾個經常使用的線程池模式。

FixedThreadPool
  1. 定長的線程池,有核心線程,核心線程的即爲最大的線程數量,沒有非核心線程。
  2. 使用的無界的等待隊列是LinkedBlockingQueue。使用時候當心堵滿等待隊列。
    在這裏插入圖片描述
SingleThreadPool

只有一條線程來執行任務,適用於有順序的任務的應用場景,也是用的界等待隊列
在這裏插入圖片描述

CachedThreadPool

可緩存的線程池,該線程池中沒有核心線程,非核心線程的數量爲Integer.max_value,就是無限大,當有須要時建立線程來執行任務,沒有須要時回收線程,適用於耗時少,任務量大的狀況。
任務隊列用的是SynchronousQueue
若是生產多快消費慢,則會致使建立不少線程需注意。
在這裏插入圖片描述

WorkStealingPool

JDK7之後 基於ForkJoinPool實現。
在這裏插入圖片描述
PS:其中FixedThreadPoolSingleThreadPoolCachedThreadPool都用的無界等待隊列,所以實際工做中都不建議這樣作的哦,阿里巴巴Java編程規範建議以下:
在這裏插入圖片描述
最後來個簡單的線程使用demo:

public class UseThreadPool
{
	// 工做線程
	static class Worker implements Runnable
	{
		private String taskName;
		private Random r = new Random();

		public Worker(String taskName)
		{
			this.taskName = taskName;
		}

		public String getName()
		{
			return taskName;
		}

		@Override
		public void run()
		{
			System.out.println(Thread.currentThread().getName() + " 當前任務: " + taskName);
			try
			{
				TimeUnit.MILLISECONDS.sleep(r.nextInt(100) * 5);
			} catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	static class CallWorker implements Callable<String>
	{
		private String taskName;
		private Random r = new Random();

		public CallWorker(String taskName)
		{
			this.taskName = taskName;
		}

		public String getName()
		{
			return taskName;
		}

		@Override
		public String call() throws Exception
		{
			System.out.println(Thread.currentThread().getName() + " 當前任務 : " + taskName);
			return Thread.currentThread().getName() + ":" + r.nextInt(100) * 5;
		}

	}

	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		ExecutorService pool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS,
				new ArrayBlockingQueue<Runnable>(10),
				new ThreadPoolExecutor.DiscardOldestPolicy());
// ExecutorService pool = Executors.newCachedThreadPool(); 
		for (int i = 0; i < 5; i++)
		{
			Worker worker = new Worker("Runnable_" + i);
			pool.execute(worker);
		}
		for (int i = 0; i < 5; i++)
		{
			CallWorker callWorker = new CallWorker("CallWorker_" + i);
			Future<String> result = pool.submit(callWorker);
			System.out.println(result.get());
		}
		pool.shutdown();
	}
}
ScheduledThreadPoolExecutor

週期性執行任務的線程池,按照某種特定的計劃執行線程中的任務,有核心線程,但也有非核心線程,非核心線程的大小也爲無限大。適用於執行週期性的任務。
在這裏插入圖片描述
看構造函數:調用的仍是ThreadPoolExecutor構造函數,區別不一樣點在於任務隊列是用的DelayedWorkQueue,沒什麼新奇的了。

在這裏插入圖片描述
核心函數講解:

  1. schedule
    只執行一次,任務還能夠延時執行,傳入待執行任務跟延時時間。
    在這裏插入圖片描述
  2. scheduleAtFixedRate
    提交固定時間間隔的任務,提交任務,延時時間,已經循環時間間隔時間。這個的含義是隻是在固定的時間間隔嘗試運行該任務。
    在這裏插入圖片描述
  3. scheduleWithFixedDelay
    提交固定延時間隔執行的任務。上一個任務執行完畢後等多久再執行下個任務,這個中間時間叫FixedDelay
    在這裏插入圖片描述
    其中scheduleAtFixedRatescheduleWithFixedDelay區別以下圖
    在這裏插入圖片描述
    scheduleAtFixedRate任務超時狀態,好比咱們設定60s執行一次,其中第一個任務時長 80s,第二個任務20s,第三個任務 50s。
  1. 第一個任務第0秒開始,第80s結束.
  2. 第二個任務第80s開始,在第100s結束.
  3. 第三個任務第120s秒開始,170s結束.
  4. 第四個任務從180s開始.

簡單Mark個循環任務demo:

class ScheduleWorker implements Runnable {
    public final static int Normal = 0;//普通任務類型
    public final static int HasException = -1;//會拋出異常的任務類型
    public final static int ProcessException = 1;//拋出異常但會捕捉的任務類型
    public static SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private int taskType;
    public ScheduleWorker(int taskType) {
        this.taskType = taskType;
    }
    @Override
    public void run() {
        if (taskType == HasException) {
            System.out.println(formater.format(new Date()) + " 異常產生");
            throw new RuntimeException("有異常");
        } else if (taskType == ProcessException) {
            try {
                System.out.println(formater.format(new Date()) + " 異常產生被捕捉");
                throw new RuntimeException("異常被捕捉");//異常致使下個任務沒法執行
            } catch (Exception e) {
                System.out.println(" 異常被主播");
            }
        } else {
            System.out.println("正常" + formater.format(new Date()));
        }
    }
}
public class SchTest{
	public static void main(String[] args) {
		ScheduledThreadPoolExecutor schedule = new ScheduledThreadPoolExecutor(1);

		schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.HasException),
				1000, 3000, TimeUnit.MILLISECONDS); // 任務在 1秒後執行 週期3秒

		schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.Normal),
				1000, 3000, TimeUnit.MILLISECONDS);
	}
}
CompletionService

JDK8中新添加的一個類,攝像一個場景你去詢問兩個商品價格而後將價格保存數據庫。

ExecutorService executor =Executors.newFixedThreadPool(2);
// 異步向電商 S1 詢價
Future<Integer> f1 = executor.submit(()->getPriceByS1());
// 異步向電商 S2 詢價
Future<Integer> f2 = executor.submit(()->getPriceByS2());

// 獲取電商 S1 報價並保存
r=f1.get();
executor.execute(()->save(r));
// 獲取電商 S2 報價並保存
r=f2.get();
executor.execute(()->save(r));

上面的這個方案自己沒有太大問題,可是有個地方的處理須要你注意,那就是若是獲取電商 S1 報價的耗時很長,那麼即使獲取電商 S2 報價的耗時很短,也沒法讓保存 S2 報價的操做先執行,由於這個主線程都阻塞在了 f1.get(),那咱們如何解決了?
解決方法:結果都存入到一個阻塞隊列中去。

// 建立阻塞隊列
BlockingQueue<Integer> bq =new LinkedBlockingQueue<>();
// 電商 S1 報價異步進入阻塞隊列 
executor.execute(()->bq.put(f1.get()));
// 電商 S2 報價異步進入阻塞隊列 
executor.execute(()->bq.put(f2.get()));
// 異步保存全部報價 
for (int i=0; i<2; i++) {
  Integer r = bq.take();
  executor.execute(()->save(r));
}

在JDK8中不建議上面的工做都手動實現,JDK提供了CompletionService ,它實現原理也是內部維護了一個阻塞隊列,它的核心功效就是讓先執行的任務先放到結果集。當任務執行結束就把任務的執行結果加入到阻塞隊列中,不一樣的是CompletionService是把任務執行結果的 Future 對象加入到阻塞隊列中,而上面的示例代碼是把任務最終的執行結果放入了阻塞隊列中。
CompletionServiceExecutorBlockingQueue的功能融合在一塊兒,CompletionService內部有個阻塞隊列。
CompletionService 接口的實現類是 ExecutorCompletionService,這個實現類的構造方法有兩個,分別是:

ExecutorCompletionService(Executor executor)
ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)

這兩個構造方法都須要傳入一個線程池,若是不指定 completionQueue,那麼默認會使用無界的 LinkedBlockingQueue。任務執行結果的 Future 對象就是加入到 completionQueue 中。

// 建立線程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 建立 CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
// 異步向電商 S1 詢價
cs.submit(()->getPriceByS1());
// 異步向電商 S2 詢價
cs.submit(()->getPriceByS2());
// 將詢價結果異步保存到數據庫
for (int i=0; i<2; i++) {
  Integer r = cs.take().get();
  executor.execute(()->save(r));
}

來一個總體的demo加深印象:

// 任務類
class WorkTask implements Callable<Integer>
{
	private String name;

	public WorkTask(String name)
	{
		this.name = name;
	}
	@Override
	public Integer call()
	{
		int sleepTime = new Random().nextInt(1000);
		try
		{
			Thread.sleep(sleepTime);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		return sleepTime;
	}
}

public class CompletionCase
{
	private final int POOL_SIZE = Runtime.getRuntime().availableProcessors();
	private final int TOTAL_TASK = Runtime.getRuntime().availableProcessors();
	public void selfByQueue() throws Exception
	{
		long start = System.currentTimeMillis();  // 統計全部任務休眠的總時長
		AtomicInteger count = new AtomicInteger(0);
		ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);  // 建立線程池
		BlockingQueue<Future<Integer>> queue = new LinkedBlockingQueue<Future<Integer>>();//容器存放提交給線程池的任務,list,map,

		for (int i = 0; i < TOTAL_TASK; i++)
		{
			Future<Integer> future = pool.submit(new WorkTask("要執行的第幾個任務" + i));
			queue.add(future);//i=0 先進隊列,i=1的任務跟着進
		}
		for (int i = 0; i < TOTAL_TASK; i++)
		{
			int sleptTime = queue.take().get(); // 檢查線程池任務執行結果 i=0先取到,i=1的後取到
			System.out.println(" 休眠毫秒數 = " + sleptTime + " ms ");
			count.addAndGet(sleptTime);
		}
		pool.shutdown();
		System.out.println("休眠時間" + count.get() + "ms,耗時時間" + (System.currentTimeMillis() - start) + " ms");
	}

	public void testByCompletion() throws Exception
	{
		long start = System.currentTimeMillis();
		AtomicInteger count = new AtomicInteger(0);
		// 建立線程池
		ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
		CompletionService<Integer> cService = new ExecutorCompletionService<>(pool);

		// 向裏面扔任務
		for (int i = 0; i < TOTAL_TASK; i++)
		{
			cService.submit(new WorkTask("執行任務" + i));
		}
		// 檢查線程池任務執行結果
		for (int i = 0; i < TOTAL_TASK; i++)
		{
			int sleptTime = cService.take().get();
			System.out.println("休眠毫秒數 = " + sleptTime + " ms ...");
			count.addAndGet(sleptTime);
		}
		pool.shutdown();
		System.out.println("休眠時間 " + count.get() + "ms,耗時時間" + (System.currentTimeMillis() - start) + " ms");
	}

	public static void main(String[] args) throws Exception
	{
		CompletionCase t = new CompletionCase();
		t.selfByQueue();
		t.testByCompletion();
	}
}

在這裏插入圖片描述

常見考題

  1. 爲何用線程池?
  2. 線程池的做用?
  3. 經常使用的線程池模版?
  4. 7大重要參數?
  5. 4大拒絕策略?
  6. 常見線程池任務隊列,如何理解有界跟無界?
    7.如何分配線程池個數?
  7. 單機線程池執行通常斷電瞭如何考慮?

正在處理的實現事務功能,下次自動回滾,隊列實現持久化儲存,下次啓動自動載入。

  1. 設定一個線程池優先級隊列,Runable類要實現可對比功能,任務隊列使用優先級隊列

在這裏插入圖片描述

參考

線程池關閉
CompletionService

相關文章
相關標籤/搜索