和麪試官說了線程池,我發現我滿嘴跑火車

對於線程池的使用一般要不然就是c+v技能,要不然就是直接使用別人初始化好的方法,至於具體要注意的細節一直沒有在意過。前段時間看到了別人面試的題目,我發現我之前的理解和別人說的東西都是在滿嘴跑火車!

 

本文風格交代: 代碼demo+總結結論,所以可以直接拉下源代碼觀看博文提到的知識點會更加舒服:

源代碼交流更加清晰

 

1、線程池的創建以及常見參數的說明

package com.sakura.rain;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName Main
 * @function [提交任務數量小於  核心+隊列]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 19:39
 */
public class Main00 {
    public static void main(String[] args) {

        /*
          int corePoolSize: 核心線程數,開始創建的時候線程池中並沒有任何線程,而是在任務到來的時候開始創建線程
           也可以預創建線程:prestartAllCoreThreads(源碼解釋:覆蓋默認當任務開始執行創建的方式,預先創建好核心線程數)
           當線程池中的任務達到核心線程數的時候,後面的任務就會排隊在任務中
           如果超出了最大排隊的數量,這個時候默認會拒絕後面的任務報錯提示,這種稱之爲拒絕策略,拒絕策略就是這段代碼需要演示的關鍵點
          int maximumPoolSize, 最大支持創建的線程數量
          long keepAliveTime, 線程保持活躍的時間,線程池中線程沒有進行中的任務空閒過長時間會被回收
          TimeUnit unit, 設置活躍時間的單位
          LinkedBlockingDeque<Runnable> workQueue, 一個基於鏈表結構的阻塞隊列
          ThreadFactory threadFactory, 線程工廠,可以設置創建出的線程名字更加具有辨識度和意義
          RejectedExecutionHandler handler 線程滿了之後的拒絕策略,常見的有四種-默認策略是:AbortPolicy
            這裏面既然牽涉到了拒絕策略,那麼就要列舉一下常見的拒絕策略了。經常用到的四種拒絕策略都是
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread.currentThread().setName("demo--" + System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName());
                return null;
            }
        }, new ThreadPoolExecutor.AbortPolicy());
        threadPoolExecutor.shutdown();



        /*
        * 下面這段代碼展示的是當排隊中的任務數量超出核心線程數
        * 核心線程數:2
        * 最大支持創建線程的數量爲: 5
        * 保持活躍的時間 3s
        * 隊列使用的是阻塞鏈表 LinkedBlockingDeque
        * */
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());

        /*在沒有調用預備創建線程核心數之前,打印線程池中的線程個數*/
        System.out.println(threadPool.getQueue());

        for (int index = 0; index < 10; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }

        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());
        System.out.println("線是否處於活躍的狀態" + threadPool.getActiveCount());

        threadPool.shutdownNow();

        /*
        打印的結果爲:
            []
            線程池中的隊列數量8
            線程池中活躍的線程數2
            線程編碼爲: 1
            線程編碼爲: 0
            線程編碼爲: 3
            線程編碼爲: 2
            線程編碼爲: 4
            線程編碼爲: 5
            線程編碼爲: 6
            線程編碼爲: 7
            線程編碼爲: 8
            線程編碼爲: 9

            線程池中的隊列數量0
            線程池中活躍的線程數0
            線是否處於活躍的狀態0

            在代碼中一共創建了 10 個線程,但是總共調用了兩個線程去執行,爲什麼不是五個:
                核心線程: 10 > 2
                隊列:    10 = 10
                任務進來之後有兩個線程放到核心線程中進行執行,那麼隊列中還有8個,默認排列到線程中繼續等待,知道前面的線程執行結束之後在拿出隊列中的線程進行執行
        * */
    }
}

2、當任務書超出設置池子大小會發生什麼

package com.sakura.rain;

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

/**
 * @ClassName 任務數量大於隊列核心之和
 * @function [提交任務數 > 核心+隊列]
 * @notice 默認拒絕策略
 * @Author lcz
 * @Date 2020/06/21 20:30
 */
public class Main01 {
    public static void main(String[] args) {
        /*
         * 下面這段代碼展示的是當排隊中的任務數量超出核心線程數
         * 核心線程數:2
         * 最大支持創建線程的數量爲: 5
         * 保持活躍的時間 3s
         * 隊列使用的是阻塞鏈表 LinkedBlockingDeque
         * */
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());

        /*在沒有調用預備創建線程核心數之前,打印線程池中的線程個數*/
        System.out.println(threadPool.getQueue());

        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
            System.out.println("當任務提交index座標爲" + index + "時,活躍的線程數爲:" + threadPool.getActiveCount());
        }

        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());

        /*爲了窺探線程數量減少的過程,執行這段循環代碼*/
        int activeCount = threadPool.getActiveCount();
        long currentTimeMillis = System.currentTimeMillis();
        while (threadPool.getActiveCount() != 0) {
            if (activeCount != threadPool.getActiveCount()) {
                long timeMillis = System.currentTimeMillis();
                System.out.println("減少時間間隔: " + (timeMillis - currentTimeMillis));
                activeCount = threadPool.getActiveCount();
                System.out.println("線程活躍數減少: " + threadPool.getActiveCount());
            }
        }


        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());
        System.out.println("線是否處於活躍的狀態" + threadPool.getActiveCount());

        threadPool.shutdownNow();

        /*
            []
            當任務提交index座標爲0時,活躍的線程數爲:1
            當任務提交index座標爲1時,活躍的線程數爲:2
            當任務提交index座標爲2時,活躍的線程數爲:2
            當任務提交index座標爲3時,活躍的線程數爲:2
            當任務提交index座標爲4時,活躍的線程數爲:2
            當任務提交index座標爲5時,活躍的線程數爲:2
            當任務提交index座標爲6時,活躍的線程數爲:2
            當任務提交index座標爲7時,活躍的線程數爲:2
            當任務提交index座標爲8時,活躍的線程數爲:2
            當任務提交index座標爲9時,活躍的線程數爲:2
            當任務提交index座標爲10時,活躍的線程數爲:2
            當任務提交index座標爲11時,活躍的線程數爲:2
            當任務提交index座標爲12時,活躍的線程數爲:3
            當任務提交index座標爲13時,活躍的線程數爲:4
            當任務提交index座標爲14時,活躍的線程數爲:5
            線程池中的隊列數量10
            線程池中活躍的線程數5
            線程編碼爲: 0
            線程編碼爲: 1
            線程編碼爲: 12
            線程編碼爲: 14
            線程編碼爲: 13
            線程編碼爲: 3
            線程編碼爲: 2
            線程編碼爲: 5
            線程編碼爲: 6
            線程編碼爲: 4
            線程編碼爲: 8
            線程編碼爲: 7
            線程編碼爲: 11
            線程編碼爲: 9
            線程編碼爲: 10

            線程池中的隊列數量0
            線程池中活躍的線程數0
            線是否處於活躍的狀態0

            在代碼中一共創建了 15 個線程,但是總共調用了5個線程去執行,爲什麼這個時候是五個:
                最開始調用核心線程數去執行,2個,隊列中的任務持續進行增長
                創建12個任務時,在下一個任務進來是發現隊列中承載的能力已經達到了最大的限度,於是便開始擴充可以執行任務的線程數
                從上面的打印結果證明這個結論是再形象不過了
        * */
    }
}

3、引出拒絕策略這個問題

package com.sakura.rain;

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

/**
 * @ClassName Main02
 * @function [提交任務數量 > 最大線程數 + 排隊隊列]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 20:47
 */
public class Main02 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());

        /*在沒有調用預備創建線程核心數之前,打印線程池中的線程個數*/
        System.out.println(threadPool.getQueue());

        for (int index = 0; index < 20; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }

        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println("線程池中的隊列數量" + threadPool.getQueue().size());
        System.out.println("線程池中活躍的線程數" + threadPool.getActiveCount());
        System.out.println("線是否處於活躍的狀態" + threadPool.getActiveCount());

        threadPool.shutdownNow();
        /*
            Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7a5d012c rejected from java.util.concurrent.ThreadPoolExecutor@79698539[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
            at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
            at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
            at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
            at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
            at com.sakura.rain.Main02.main(Main02.java:23)
            線程編碼爲: 14
            線程編碼爲: 0
            線程編碼爲: 13
            線程編碼爲: 12
            線程編碼爲: 1
            線程編碼爲: 2
            線程編碼爲: 4
            線程編碼爲: 6
            線程編碼爲: 3
            線程編碼爲: 5
            線程編碼爲: 7
            線程編碼爲: 8
            線程編碼爲: 9
            線程編碼爲: 10
            線程編碼爲: 11

           上面的運行結果可以看出,已經提交到線程池中任務還在繼續執行直到結束,但是後續超過15的線程全部執行失敗了,並且在程序中拋出了異常
           我們在最後還有幾行打印的輸出也沒有執行,說明程序異常拋出之後影響到了後面的代碼的執行。

           這種用上了最大啓用線程數加上隊列總和都沒有辦法承載的出現的異常處理的方式叫做線程的拒絕策略,在ThreadPoolExecutor中有四個內部實現類:
            AbortPolicy
            DiscardPolicy
            DiscardOldestPolicy
            CallerRunsPolicy
           接下來的MainDemo中演示這四種拒絕策略的區別

        */
    }
}

4、默認拒絕策略 AbortPloicy

package com.sakura.rain;

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

/**
 * @ClassName Main03
 * @function [提交的任務數量大於最大線程數加上隊列]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 21:01
 */
public class Main03 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.AbortPolicy());

        /*在沒有調用預備創建線程核心數之前,打印線程池中的線程個數*/
        System.out.println("調用創建核心線程初始化方法之前: " + threadPool.getActiveCount());
        /*再調用預備創建核心線程數之後,打印線程池中線程的個數*/
        threadPool.prestartAllCoreThreads();
        System.out.println("調用創建核心線程初始化方法之後:" + threadPool.getActiveCount());


        /*休眠時間大於設置的默認活躍時間*/
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("休眠時間大於默認的活躍時間之後:" + threadPool.getActiveCount());



        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        threadPool.shutdown();
        /*
        *
        *   調用創建核心線程初始化方法之前: 0
            調用創建核心線程初始化方法之後:1
            Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6cd8737 rejected from java.util.concurrent.ThreadPoolExecutor@13969fbe[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
            at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
            at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
            at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
            at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
            at com.sakura.rain.Main03.main(Main03.java:26)
            線程編碼爲: 0
            線程編碼爲: 2
            線程編碼爲: 1
            線程編碼爲: 3

            在進行對拒絕策略說明之前,這裏先說明兩個方法:prestartCoreThread() and prestartAllCoreThreads()
            默認創建的線程池中是沒有執行的線程的,調用這個方法之後,會創建出默認核心數量的活躍線程

            代碼在創建線程池的時候執行了線程池的拒絕策略:AbortPolicy()
            這種策略就是在上述任務數大於最大線程+隊列容量情況時,對後續提交的任務進行拒絕策略,但是這種失敗的拒絕提示會拋出異常。
            出現異常的目的是爲了能夠及時發現問題,或者是針對問題的任務進行特殊處理或者是操作。

            拒絕策略是實現了接口:RejectedExecutionHandler
            重寫了其中的:rejectedExecution()方法
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
            }

        * */

    }
}

5、拒絕策略之DiscardPolicy

package com.sakura.rain;

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

/**
 * @ClassName Main04
 * @function [業務功能]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 21:17
 */
public class Main04 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.DiscardPolicy());


        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        threadPool.shutdown();

        /*
        *
        * Connected to the target VM, address: '127.0.0.1:61902', transport: 'socket'
            線程編碼爲: 0
            線程編碼爲: 3
            線程編碼爲: 2
            線程編碼爲: 1


            拒絕策略: DiscardPolicy()
            超出能力範圍內的加入失敗不拋出異常,但是後續線程池中不會執行這些方法
            重寫方法中的處理邏輯爲:
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

            }
        * */

    }
}

6、拒絕策略之DiscardOldestPolicy

package com.sakura.rain;

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

/**
 * @ClassName Main04
 * @function [業務功能]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 21:17
 */
public class Main05 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.DiscardOldestPolicy());


        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        threadPool.shutdown();

        /*

        拒絕策略: DiscardOldestPolicy()
        超出能力範圍內的加入失敗不拋出異常,但是後續線程池中不會執行這些方法
        重寫方法中的處理邏輯爲:
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
        * */

    }
}

7、拒絕策略之CallerRunsPolicy

package com.sakura.rain;

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

/**
 * @ClassName Main06
 * @function [業務功能]
 * @notice 持久化操作不做業務上處理
 * @Author lcz
 * @Date 2020/06/21 21:27
 */
public class Main06 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());


        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPool.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
        threadPool.shutdown();

        /*
        * 代碼執行的結果:
            線程編碼爲: 0
            線程編碼爲: 3
            線程編碼爲: 4
            線程編碼爲: 6
            線程編碼爲: 2
            線程編碼爲: 1
            線程編碼爲: 8
            線程編碼爲: 7
            線程編碼爲: 5
            線程編碼爲: 9
            線程編碼爲: 11
            線程編碼爲: 10
            線程編碼爲: 13
            線程編碼爲: 12
            線程編碼爲: 14
        *
        文檔中解釋爲:只要線程在則會等待直到所有的方法都執行

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
        * */


        /*因此我在下面這段代碼中使用方法強制關閉,演示在線程池關閉之後,後續的方法不再執行*/
        ThreadPoolExecutor threadPoolClose = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());


        for (int index = 0; index < 15; index++) {
            int finalIndex = index;
            threadPoolClose.submit(() -> {
                Thread.currentThread().setName("線程編碼爲: " + finalIndex);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
            if (index == 10) {
                threadPoolClose.shutdownNow();
                System.out.println("線程池現在的狀態爲:");
                System.out.println(threadPoolClose.isShutdown());
            }
        }

        /*
        *   線程編碼爲: 4
            線程編碼爲: 0
            線程編碼爲: 3
            線程編碼爲: 2
            線程編碼爲: 5
            線程編碼爲: 1
            Disconnected from the target VM, address: '127.0.0.1:50705', transport: 'socket'
            java.lang.InterruptedException: sleep interrupted
            at java.lang.Thread.sleep(Native Method)
            at com.sakura.rain.Main06.lambda$main$0(Main06.java:70)
            at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
            at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
            at java.util.concurrent.FutureTask.run(FutureTask.java)
            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)
            線程編碼爲: 9
            線程編碼爲: 6
            線程編碼爲: 7
            線程池現在的狀態爲:
            true
            線程編碼爲: 8

            執行結果看得出這個玩意真的不再執行了
            */
    }
}

花了好幾個晚上整理了這些代碼demo,代碼中的言辭和觀點都是個人理解,不足之處還望指正。

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