線程池4種常用方式實現以及自定義線程池原理

1.什麼是線程池

Java中的線程池是運用場景最多的併發框架,幾乎所有需要異步或併發執行任務的程序
都可以使用線程池。在開發過程中,合理地使用線程池能夠帶來3個好處。
第一:降低資源消耗。 通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
第二:提高響應速度。 當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。 線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用

2.常用的4種線程池

2.1分類

(1)newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
(2)newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
(3)newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
(4)newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

2.2實現

2.2.1 newCachedThreadPool

public class NewCacheThreadPoolTest {
    public static void main(String[] args) {
        // 無限大小線程池 jvm自動回收
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newCachedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {

                    }
                    System.out.println(Thread.currentThread().getName() + ",i:" + temp);

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

2.2.2 newFixedThreadPool

public class NewFixedThreadPoolTest {
    public static void main(String[] args) {
        // 固定大小的線程數
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {

                    }
                    System.out.println(Thread.currentThread().getName() + ",i:" + temp);

                }
            });
        }
        newFixedThreadPool.shutdown();

    }
}

**2.2.3 newScheduledThreadPool **

public class NewScheduledThreadPoolTest {
    public static void main(String[] args) {
        // 固定大小的線程數
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "---i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);
        }
        newScheduledThreadPool.shutdown();

    }
}

2.2.4 newSingleThreadExecutor

public class NewSingleThreadExecutorTest {
    public static void main(String[] args) {
        //單線程,主要用於依賴上一個結果的場景
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newSingleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "---i:" + temp);
                }
            });
        }
        newSingleThreadExecutor.shutdown();
    }
}

3.自定義線程池

阿里的 Java開發手冊,上面有線程池的一個建議:

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,
這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

點擊ThreadPoolExecutor 源碼

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

corePoolSize - 線程池核心池的大小。
maximumPoolSize - 線程池的最大線程數。
keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
unit - keepAliveTime 的時間單位。
workQueue - 用來儲存等待執行任務的隊列。
threadFactory - 線程工廠。
handler - 拒絕策略。

關注點1 線程池大小
線程池有兩個線程數的設置,一個爲核心池線程數,一個爲最大線程數。
在創建了線程池後,默認情況下,線程池中並沒有任何線程,等到有任務來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法
當創建的線程數等於 corePoolSize 時,會加入設置的阻塞隊列。當隊列滿時,會創建線程執行任務直到線程池中的數量等於maximumPoolSize。

關注點2 適當的阻塞隊列
java.lang.IllegalStateException: Queue full
方法 拋出異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列

PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
DelayQueue: 一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue: 一個不存儲元素的阻塞隊列。
LinkedTransferQueue: 一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque: 一個由鏈表結構組成的雙向阻塞隊列。

關注點3 明確拒絕策略
ThreadPoolExecutor.AbortPolicy: 丟棄任務並拋RejectedExecutionException異常。 (默認)
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

自定義線程池原理圖

在這裏插入圖片描述

實現實例

**
 * @author Administrator
 */
public class MyThreadPool {
    public static void main(String[] args) {
        /**
         * corePoolSize核心線程數 1
         * maximumPoolSize最大線程數 2
         * keepAliveTime保持活躍時間 0
         * capacity有界隊列的大小 3
         */
        ThreadPoolExecutor threadPoolExecutor = new
                ThreadPoolExecutor(1,
                2,
                0L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3));
        /**
         * 任務1不小於核心線程數,創建線程執行
         */
        threadPoolExecutor.execute(new ThreadTest("任務1"));
        /**
         * 任務2大於核心線程數,但是隊列未滿,入列,隊列爲1
         */
        threadPoolExecutor.execute(new ThreadTest("任務2"));
        /**
         * 任務3大於核心線程數,但是隊列未滿,入列,隊列爲2
         */
        threadPoolExecutor.execute(new ThreadTest("任務3"));
        /**
         * 任務4大於核心線程數,但是隊列未滿,入列,隊列爲3,此時隊列已滿
         */
        threadPoolExecutor.execute(new ThreadTest("任務4"));
        /**
         * 任務5隊列已滿,但是沒有大於最大線程數,重新創建線程執行
         */
        threadPoolExecutor.execute(new ThreadTest("任務5"));
        /**
         * 任務6隊列已滿,大於了最大線程數,拒絕任務執行
         */
        threadPoolExecutor.execute(new ThreadTest("任務6"));
        threadPoolExecutor.shutdown();
    }

}

class ThreadTest implements Runnable {
    private String threadName;

    public ThreadTest(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void run() {
        System.out.println(threadName);
    }
}

四、合理配置線程池的大小

遵循兩原則:
1、如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 CPU N+1
2、如果是IO密集型任務,參考值可以設置爲2*N CPU
當然,這只是一個參考值,具體的設置還需要根據實際情況進行調整,比如可以先將線程池大小設置爲參考值,再觀察任務運行情況和系統負載、資源利用率來進行適當調整。

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