Java 自定義線程池的線程工廠


  本文分享創建線程工廠 ThreadFactory 的三種方式,以方便大家快速創建線程池,並通過線程工廠給每個創建出來的線程設置極富業務含義的名字。

線程池大小考慮因素

  由於需要自定義線程池,故這里先介紹線程池大小如何設定最為合理。我們需要分析計算環境、資源預算和任務的特性,例如,考慮一下在部署的服務器上有多少個CPU,內存是多大,任務是計算密集型、I/O密集型還是二者皆可。

  對於計算密集型的任務,在擁有N(CPU)個處理器的服務器上,當線程池的大小為N+1時,通常能實現最優的利用率。對於包含I/O操作或者其它阻塞操作的任務,由於線程並不會一直執行,因此線程池的規模應該更大,參考值可以設置為2**N(CPU)。線程池資源並不是唯一影響線程池大小的資源,還包括內存、文件句柄、套接字句柄和數據庫連接等。在觀察任務運行情況和系統負載、資源利用率之后,請酌情調整。

  在Java中,可以使用如下代碼獲取CPU的數量:

int N(CPU)= Runtime.getRuntime().availableProcessors();

基於此可以設置合適的線程池數量,提高系統穩定性和吞吐率。

Spring CustomizableThreadFactory

  此方式是利用Spring 框架提供的輪子 CustomizableThreadFactory。

ThreadFactory springFactory = new CustomizableThreadFactory("spring-pool-");
ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(100), springFactory);
exec.submit(() -> {
    logger.info("--記憶中的顏色是什么顏色---");
});

guava ThreadFactoryBuilder

  使用Google 開源框架guava提供的 ThreadFactoryBuilder 可以快速給線程池里的線程設置有意義的名字。

ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-").build();


ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(100),guavaFactory );
exec.submit(() -> {
    logger.info("--記憶中的顏色是什么顏色---");
});

此方法需要引入如下guava maven坐標:

     <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>

Apache commons-lang3 BasicThreadFactory

  Apache commons-lang3 提供的 BasicThreadFactory.

ThreadFactory basicFactory = new BasicThreadFactory.Builder()
        .namingPattern("basicFactory-pool-").build();

ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(100),basicFactory );
exec.submit(() -> {
    logger.info("--記憶中的顏色是什么顏色---");
});

  使用的列隊是無界隊列LinkedBlockingQueue。如果當前執行任務數量大於核心線程數,此時再提交任務就在添加到阻塞隊列中等待執行,直到有可用線程。溫馨提示,無界阻塞隊列可能消耗很大的內存資源。值得注意的是,如果使用了無界的任務隊列,線程池最大數量這個參數就沒什么效果了。

優雅的自定義線程工廠

  除了使用第三方jar包之外,我們也可以基於ThreadPoolExecutor優雅地自定義ThreadFactory,並根據自己的需要來操作線程,下面是實例代碼:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
public class ThreadPoolStudy {
    public static void main(String[] args) {
        ExecutorService executor = initExecutor();
        // 若i>0,則不會拋出異常信息。設為0是為了驗證如何捕捉異常
        for (int i = 0; i < 10; i++) {
            DivTask myTask = new DivTask(100, i);
            Future future = executor.submit(myTask);
            try {
                // 阻塞方法,盡量不要調用,這里調用get是為了避免任務執行異常被吞掉
                future.get();
            } catch (Exception e) {
                log.error("任務執行異常,", e);
            }
        }
        executor.shutdown();
        log.info("線程池馬上關閉,不再接收新任務。");
    }
    // 自定義任務,用於求兩數相除的結果
    class DivTask implements Runnable {
        int a, b;

        public DivTask(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public void run() {
            log.info("計算結果:{}", a / b);
        }
    }

    /**
     * 初始化線程池
     *
     * @return
     */
    private static ExecutorService initExecutor() {
        ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-").build();
        ExecutorService executor = new ThreadPoolExecutor(1, 64,
                30L, TimeUnit.MILLISECONDS,
                new SynchronousQueue<Runnable>(), guavaFactory);

        return executor;
    }
}

  任務執行完畢后,請關閉線程池,及時降低資源損耗。

小結

  作為程序員,要有“刨根問底”的精神。知其然,更要知其所以然。這篇文章希望能幫助你對自定義線程工廠抽絲剝繭,還原背后的原理。

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM