併發編程:JDK線程框架Executor和自定義線程池

一、Executor介紹

爲了更好的控制多線程,JDK提供了一套線程框架Executor,在java.util.concurrent包中。其中有一個比較重要的類:Executors,它扮演線程工廠的角色,可以通過Executors可以創建特定功能的線程池。

二、Executors創建線程的方法

        /**
         * 創建固定數量的線程池,該方法的線程數始終不變。
         * 當有任務提交時,若線程池空閒,則立即執行 ;若沒有,則會暫存在一個任務隊列當中等待有空閒的線程去執行
         */
        Executors.newFixedThreadPool(5);
        /**
         * 創建一個線程的線程池。
         * 當有任務提交時,若線程池空閒,則立即執行 ;若沒有,則會暫存在一個任務隊列當中等待有空閒的線程去執行
         */
        Executors.newSingleThreadExecutor();
        /**
         * 創建一個可根據實際情況調整線程數量的線程池,不限制最大線程數量。
         * 有任務時,創建線程;無任務,不創建線程。
         * 如果沒有任務60s後自動回收空閒線程
         */
        Executors.newCachedThreadPool();
        /**
         * 該方法返回ScheduledExecutorService對象。該線程池可以指定線程數量。
         */
        Executors.newScheduledThreadPool(10);

以上四種方法實際上都是通過ThreadPoolExecutor來實現的,當以上四種線程池不能滿足需求時,可以使用ThreadPoolExecutor來創建自定義線程池。

三、自定義線程池

ThreadPoolExecutor的參數

    /**
     *
     * @param corePoolSize 線程池初始化時,創建的線程數量
     * @param maximumPoolSize 線程池最大支持的線程數量
     * @param keepAliveTime 空閒線程存活的時間
     * @param unit 空閒線程存活時間的單位 keepAliveTime和unit一起使用
     * @param workQueue 當線程池沒有空閒線程時,用來緩存任務的隊列
     * @param threadFactory 
     * @param handler 如果沒有空閒線程,用來緩存隊列也滿的時候,採取的拒絕策略
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler
                              ) { ...}

瞭解了ThreadPoolExecutor之後,再來看一下JDK封裝好的線程池。

1、newFixedThreadPool源碼

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

corePoolSize和maximumPoolSize都是一樣的,即線程數量固定;

keepAliveTime爲0,即線程一直會存在,不會被回收;

這裏用的緩存隊列爲無界的LinkedBlockingQueue。

2、newSingleThreadExecutor源碼

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

coreSize和maximumPoolSize都是1,即線程池中只能有1個線程;

3、newCachedThreadPool源碼

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

corePoolSize爲0,maximum爲0x7fffffff,即最大int,在這裏認爲是無限大;

空閒線程的存活的時間爲60s;

SynchrousQueue中不能存放任何元素;

當有任務需要處理時,該線程池就會創建線程執行任務,執行完畢後,線程60s後會被回收。

4、newScheduledThreadPool源碼

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

maximumPoolSize爲無限大;

這裏用的緩存隊列爲DelayedWorkQueue,延遲隊列;

四、自定義線程池的使用詳情

構造方法中的隊列類型對線程池的使用有很大影響:

1、使用有界隊列時:

若有新的任務需要執行,如果線程池中的線程數小於corePoolSize,則會創建線程,執行任務;若大於corePoolSize,則會將任務加入到隊列中:如果隊列已滿,在總線程數不大於maximumPoolSize前提下,創建線程;若總線程數大於maximumPoolSize,則執行拒絕策略。

2、使用無界隊列時:

有新的任務需要執行時,如果線程池中的線程數小於corePoolSize,則會創建線程,執行任務;當達到corePoolSize時,就不會繼續增加。若後續仍有任務需要執行時,則任務直接進入隊列,直至系統資源耗盡或有空閒線程。

五、代碼

使用有界隊列時:

package ConcurrentProgramming.High.ThreadPoolExecute1;

import lombok.Data;

/**
 * @Author: zdj
 * @Description: 任務 需要實現Runnable接口
 * @Date: 2019年03月18日 16:28
 */
@Data
public class Task implements Runnable{

    private String id;
    private String name;

    public Task(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public void run() {
        System.out.println("run Task Id " + this.id);
        try {
            Thread.sleep(5*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String toString(){
        return id;
    }
}
package ConcurrentProgramming.High.ThreadPoolExecute1;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author: zdj
 * @Description: 自定義拒絕策略 必須實現RejectedExecutionHandler接口
 * @Date: 2019年03月18日 16:53
 */
public class MyReject implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("該任務被拒絕了,被拒絕任務的id爲" + r.toString());
    }
}
package ConcurrentProgramming.High.ThreadPoolExecute1;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: zdj
 * @Description: 自定義線程池使用有界隊列的時候
 *               創建6個任務:任務1會被直接執行,任務234會被放入有界隊列ArrayBlockingQueue中,
 *               繼續創建線程執行任務5,此時隊列已滿,也沒有空閒線程,任務6,會執行拒絕策略
 * @Date: 2019年03月18日 16:25
 */
public class UseThreadPoolExecute1 {
    public static void main(String[] args) {
        /**
         * 在使用有界隊列時,若有新的任務需要執行,如果線程池實際線程數小於corePoolSize,則優先創建線程,
         * 若大於corePoolSize,則會將任務加入隊列,
         * 若隊列已滿,則在總線程數不大於maximumPoolSize的前提下,創建新的線程,
         * 若線程數大於maximumPoolSize,則執行拒絕策略。或其他自定義方式。
         *
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
          1, 2,60,
                TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3),
                //拒絕策略 一般使用自定義的
//                new ThreadPoolExecutor.DiscardOldestPolicy()
                //自定義拒絕策略
                new MyReject()
        );


        Task task1 =new Task("1","任務1");
        Task task2 =new Task("2","任務2");
        Task task3 =new Task("3","任務3");
        Task task4 =new Task("4","任務4");
        Task task5 =new Task("5","任務5");
        Task task6 =new Task("6","任務6");

        threadPoolExecutor.execute(task1);
        threadPoolExecutor.execute(task2);
        threadPoolExecutor.execute(task3);
        threadPoolExecutor.execute(task4);
        threadPoolExecutor.execute(task5);
        threadPoolExecutor.execute(task6);

        //關閉線程池
        threadPoolExecutor.shutdown();
    }
}

運行結果:(打印順序和線程執行順序可能會有差別)

使用無界隊列時:

package ConcurrentProgramming.High.ThreadPoolExecute2;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: zdj
 * @Description: 任務 需要實現Runnable接口
 * @Date: 2019年03月18日 17:03
 */
public class Task implements Runnable{

    private static AtomicInteger count = new AtomicInteger(0);

    public void run() {
        int temp = count.incrementAndGet();
        System.out.println("任務" + temp);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package ConcurrentProgramming.High.ThreadPoolExecute2;

import java.util.concurrent.*;

/**
 * @Author: zdj
 * @Description: 線程池中使用無界隊列
 *               20個任務,線程池開啓5個線程之後,剩下的15個任務暫時放到隊列LinkedBlockingQueue中,
 *               知道有空閒線程執行去執行這些任務
 * @Date: 2019年03月18日 17:06
 */
public class Main {
    public static void main(String[] args) throws Exception{

        BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<Runnable>();

        ExecutorService executorService = new ThreadPoolExecutor(
                5,10,120L, TimeUnit.SECONDS,blockingQueue
        );
        for (int i = 0; i < 20; i++){
            executorService.execute(new Task());
        }
        Thread.sleep(1000);
        System.out.println("隊列大小爲:"+ blockingQueue.size());
        Thread.sleep(2000);

        executorService.shutdown();
    }
}

 

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