多線程系列--線程池--Executors

簡介

常見線程池種類

種類

核心線程數

最大線程數

描述

SingleThreadPool

1

1

只一個線程在工作,相當於單線程順序串行執行所有任務。

FixedThreadPool

存在

無(雖然有但實際不起作用)

 

CachedThreadPool

0

無限大

動態增刪線程數

ScheduleThreadPool

存在

無限大

週期性執行任務

常用方法

方法

作用

說明

ThreadPoolExecutor參數

newFixed

ThreadPool

創建固定大小的線程池。

每提交一個任務就創建一個線程,直到線程數到線程池的最大數

線程池的大小一旦達到最大值就會保持不變。

若某線程因異常而結束,線程池會補充一個新線程。

nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()

//nThreads是傳進來的參數

newSingle

ThreadExecutor

創建一個單線程的線程池。

 

只一個線程在工作,相當於單線程串行執行所有任務。

若這個唯一的線程因爲異常結束,會有一個新的線程來替代它。

此線程池保證所有任務的執行順序按照任務的提交順序執行

1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()

newSingleThread

ScheduledExecutor

創建一個單線程的定時線程池。

此線程池支持定時以及週期性執行任務的需求。

return new DelegatedScheduledExecutorService

(newScheduledThreadPoolExecutor(1));

newCached

ThreadPool

創建一個可緩存的線程池。

 

若線程池的數量超過了處理任務所需要的線程,就回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。

此線程池大小爲Integer的最大值。

0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()

newScheduled

ThreadPool

創建一個固定大小的定時線程池。

此線程池支持定時以及週期性執行任務的需求。

其他網址:https://www.jianshu.com/p/925dba9f5969

corePoolSize, Integer.MAX_VALUE,

0, NANOSECONDS,
new DelayedWorkQueue()

//corePoolSize是傳進來的參數

這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。

FixedThreadPool

簡介

FixedThreadPool的execute流程

參考網址:線程池的幾種實現方式 - 簡書

對上圖的說明如下。

  1)如果當前運行的線程數少於corePoolSize(即線程池中無運行的線程),則創建一個新線程來執行任務。
  2)在線程池完線程的創建之後,將任務加入Linked- BlockingQueue。
  3)線程會在一個無限循環中反覆從LinkedBlockingQueue獲取任務來執行。

        FixedThreadPool使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列(隊列的容量爲 Integer.MAX_VALUE)。使用無界隊列作爲工作隊列會對線程池帶來如下影響:

1)線程池中的線程數達到corePoolSize後,新任務將在無界隊列中等待,因此線程池中的線程數不會超過corePoolSize。
2)由於1,使用無界隊列時maximumPoolSize將是一個無效參數。 
3)由於1和2,使用無界隊列時keepAliveTime將是一個無效參數。
4)由於使用無界隊列,運行中的FixedThreadPool(未執行方法shutdown()或 shutdownNow())不會拒絕任務(不會調用RejectedExecutionHandler.rejectedExecution方法)。

實例

package org.example.a;

import java.util.concurrent.*;

class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " " + "正在執行task "+taskNum);
        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "-------------------執行task "+taskNum + " 完畢!!!");
    }
}

public class Demo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for(int i=0;i<6;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
        }
        executor.shutdown();
    }
}

執行結果

pool-1-thread-2 正在執行task 1
pool-1-thread-3 正在執行task 2
pool-1-thread-1 正在執行task 0
pool-1-thread-3 -------------------執行task 2 完畢!!!
pool-1-thread-3 正在執行task 3
pool-1-thread-2 -------------------執行task 1 完畢!!!
pool-1-thread-2 正在執行task 4
pool-1-thread-1 -------------------執行task 0 完畢!!!
pool-1-thread-1 正在執行task 5
pool-1-thread-1 -------------------執行task 5 完畢!!!
pool-1-thread-3 -------------------執行task 3 完畢!!!
pool-1-thread-2 -------------------執行task 4 完畢!!!

SingleThreadPool

簡介

SingleThreadPool的execute流程

參考網址:線程池的幾種實現方式 - 簡書

對上圖的說明如下。

    1)如果當前運行的線程數少於corePoolSize(即線程池中無運行的線程),則創建一個新線程來執行任務。
    2)在線程池完線程的創建之後,將任務加入Linked- BlockingQueue。
    3)線程會在一個無限循環中反覆從LinkedBlockingQueue獲取任務來執行。

實例

package org.example.a;

import java.util.concurrent.*;

class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " " + "正在執行task "+taskNum);
        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "-------------------執行task "+taskNum + " 完畢!!!");
    }
}

public class Demo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        for(int i=0;i<6;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
        }
        executor.shutdown();
    }
}

執行結果

pool-1-thread-1 正在執行task 0
pool-1-thread-1 -------------------執行task 0 完畢!!!
pool-1-thread-1 正在執行task 1
pool-1-thread-1 -------------------執行task 1 完畢!!!
pool-1-thread-1 正在執行task 2
pool-1-thread-1 -------------------執行task 2 完畢!!!
pool-1-thread-1 正在執行task 3
pool-1-thread-1 -------------------執行task 3 完畢!!!
pool-1-thread-1 正在執行task 4
pool-1-thread-1 -------------------執行task 4 完畢!!!
pool-1-thread-1 正在執行task 5
pool-1-thread-1 -------------------執行task 5 完畢!!!

CachedThreadPool

簡介

CachedThreadPool的execute流程

參考網址:線程池的幾種實現方式 - 簡書

對上圖的說明如下。

        首先執行SynchronousQueue.offer(Runnable task)。如果當前maximumPool中有空閒線程正在執行      
 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那麼主線程執行offer操作與空閒線程執行的poll操作配對成功,主線程把任務交給空閒線程執行,execute()方法執行完成;否則執行下面的步驟

        當初始maximumPool爲空,或者maximumPool中當前沒有空閒線程時,將沒有線程執行 
SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這種情況下,步驟(1)將失敗。此時CachedThreadPool會創建一個新線程執行任務,execute()方法執行完成。

        在步驟(2)中新創建的線程將任務執行完後,會執行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這個poll操作會讓空閒線程最多在SynchronousQueue中等待60秒鐘。如果60秒鐘內主線程提交了一個新任務(主線程執行步驟(1)),那麼這個空閒線程將執行主線程提交的新任務;否則,這個空閒線程將終止。由於空閒60秒的空閒線程會被終止,因此長時間保持空閒的CachedThreadPool不會使用任何資源。

        前面提到過,SynchronousQueue是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主線程提交的任務傳遞給空閒線程執行。CachedThreadPool中任務傳遞的示意圖如圖所示。

CachedThreadPool是一個會根據需要創建新線程的線程池。下面是創建CachedThreadPool的源代碼。
public static ExecutorService newCachedThreadPool() {     
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue());
}
        CachedThreadPool的corePoolSize被設置爲0,即corePool爲空;maximumPoolSize被設置爲 Integer.MAX_VALUE,即maximumPool是無界的。這裏把keepAliveTime設置爲60L,意味着 CachedThreadPool中的空閒線程等待新任務的最長時間爲60秒,空閒線程超過60秒後將會被終止。
        FixedThreadPool和SingleThreadExecutor使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列。CachedThreadPool使用沒有容量的SynchronousQueue作爲線程池的工作隊列,但 CachedThreadPool的maximumPool是無界的。這意味着,如果主線程提交任務的速度高於 maximumPool中線程處理任務的速度時,CachedThreadPool會不斷創建新線程。極端情況下, 
CachedThreadPool會因爲創建過多線程而耗盡CPU和內存資源

SingleScheduledExecutorService

簡介

其他網址

ScheduledExecutorService的scheduleAtFixedRate和scheduleWithFixedDelay方法的區別_18875541的博客-CSDN博客

另見:《Java併發編程之美》=> 第9章 Java 併發包中ScheduledThreadPoolExecutor 原理探究

方法 說明 備註
scheduleAtFixedRate 以上一次任務的開始時間爲間隔的,並且當任務執行時間大於設置的間隔時間時,真正間隔的時間由任務執行時間爲準 scheduleAtFixedRate和scheduleWithFixedRate第2個參數:延時多長時間之後運行第一次任務
scheduleWithFixedDelay 以上次任務的結束時間爲間隔,即:上一個任務結束後,延時定義的時間間隔,然後再次執行。

實例

程序1:(間隔時間(3s)大於任務的執行時間(1s))

package org.example.a;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

class MyTask implements Runnable {
    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            System.out.println(sdf.format(new Date()) + " " + "正在執行task");
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------------執行task " + " 完畢!!!");
    }
}

public class Demo {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(new MyTask(), 0, 3, TimeUnit.SECONDS);
        // executor.scheduleWithFixedRate(new MyTask(), 0, 3, TimeUnit.SECONDS);
        //不能加這句
        //executor.shutdown();
    }
}

運行結果

scheduleAtFixedRate:可見,每3秒運行一次

22:54:15 正在執行task
-------------------執行task  完畢!!!
22:54:18 正在執行task
-------------------執行task  完畢!!!
22:54:21 正在執行task
-------------------執行task  完畢!!!
22:54:24 正在執行task
-------------------執行task  完畢!!!
22:54:27 正在執行task
-------------------執行task  完畢!!!
22:54:30 正在執行task
-------------------執行task  完畢!!!
22:54:33 正在執行task
-------------------執行task  完畢!!!

 scheduleWithFixedRate:可見,每4秒運行一次

23:04:08 正在執行task
-------------------執行task  完畢!!!
23:04:12 正在執行task
-------------------執行task  完畢!!!
23:04:16 正在執行task
-------------------執行task  完畢!!!
23:04:20 正在執行task
-------------------執行task  完畢!!!
23:04:24 正在執行task
-------------------執行task  完畢!!!
23:04:28 正在執行task

程序2 :(間隔時間(3s)小於任務的執行時間(4s))

package org.example.a;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

class MyTask implements Runnable {
    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            System.out.println(sdf.format(new Date()) + " " + "正在執行task");
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-------------------執行task " + " 完畢!!!");
    }
}

public class Demo {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedDelay(new MyTask(), 0, 3, TimeUnit.SECONDS);
        //executor.scheduleWithFixedDelay(new MyTask(), 0, 3, TimeUnit.SECONDS);
        //這句不要寫
        //executor.shutdown();
    }
}

運行結果

scheduleAtFixedRate:可見,此時間隔時間失效,會以任務執行時間爲準。上一個任務一結束就開始下一個任務

22:58:53 正在執行task
-------------------執行task  完畢!!!
22:58:57 正在執行task
-------------------執行task  完畢!!!
22:59:01 正在執行task
-------------------執行task  完畢!!!
22:59:05 正在執行task
-------------------執行task  完畢!!!
22:59:09 正在執行task
-------------------執行task  完畢!!!

 scheduleWithFixedRate:

23:08:31 正在執行task
-------------------執行task  完畢!!!
23:08:38 正在執行task
-------------------執行task  完畢!!!
23:08:45 正在執行task
-------------------執行task  完畢!!!
23:08:52 正在執行task
-------------------執行task  完畢!!!
23:08:59 正在執行task
-------------------執行task  完畢!!!

異常處理

        若程序中捕獲了異常,則不影響下一個任務執行,若不捕獲,則會影響下一個任務執行。

程序1:不捕獲異常

package org.example.a;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

class MyTask implements Runnable {
    int i = 0;
    int[] a = {1, 2, 3};
    @Override
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println(sdf.format(new Date()) + " " + "正在執行task");
        System.out.println(a[i++]);
        System.out.println("-------------------執行task " + " 完畢!!!");
    }
}

public class Demo {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleWithFixedDelay(new MyTask(), 0, 1, TimeUnit.SECONDS);
        //這句不要寫
        //executor.shutdown();
    }
}

運行結果(異常後,直接卡死)

23:15:28 正在執行task
1
-------------------執行task  完畢!!!
23:15:29 正在執行task
2
-------------------執行task  完畢!!!
23:15:30 正在執行task
3
-------------------執行task  完畢!!!
23:15:31 正在執行task

程序2:捕獲異常

package org.example.a;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

class MyTask implements Runnable {
    int i = 0;
    int[] a = {1, 2, 3};
    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            System.out.println(sdf.format(new Date()) + " " + "正在執行task");
            System.out.println(a[i++]);
            System.out.println("-------------------執行task " + " 完畢!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleWithFixedDelay(new MyTask(), 0, 1, TimeUnit.SECONDS);
        //這句不要寫
        //executor.shutdown();
    }
}

執行結果(一直往下執行)

23:17:10 正在執行task
1
-------------------執行task  完畢!!!
23:17:11 正在執行task
2
-------------------執行task  完畢!!!
23:17:12 正在執行task
3
-------------------執行task  完畢!!!
23:17:13 正在執行task
java.lang.ArrayIndexOutOfBoundsException: 3
	at org.example.a.MyTask.run(Demo.java:15)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
23:17:14 正在執行task
java.lang.ArrayIndexOutOfBoundsException: 4
	at org.example.a.MyTask.run(Demo.java:15)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
23:17:15 正在執行task
java.lang.ArrayIndexOutOfBoundsException: 5
	at org.example.a.MyTask.run(Demo.java:15)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

 

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