Sentinel源碼-線程池的使用

前言:

    有好長一段時間沒有寫博客了,思來想去,懶是主要的原因,另外一方面筆者在年初換了工作,適應工作還是花了蠻長一段時間的,還有就是,沒找到一個合適的主題、系列來寫。

    之前寫的很多對個人的成長還是比較有益的,起碼現在在看源碼的時候不像前兩年那麼吃力了,寫的代碼也會刻意去模仿源碼的風格來做。

    現在找到一個合適的主題,就是Sentinel,因爲公司未來要引入這項技術,所以筆者先前期研究下。

    看代碼多了的好處就是,大家處理問題的方式大都大同小異,優秀的方式大家都在相互借鑑。筆者也想把這些閃光點單獨整理出來。所以暫時的這個系列不會像之前Spring那樣給與整體框架分析,而是反過來,從細微處見真章。這樣印象應該會更深刻些。當然在最後的時候也是要好好分析一下整體框架的。

    注意:後面所使用的Sentinel版本爲1.7-Realease版本

 

1.JDK中關於線程池的使用

    線程池是我們大家都比較熟悉的一個點了,當我們想併發執行任務時,首先想到的就是開啓多個線程來執行。

    最簡單的方式莫過於直接創建一個線程任務,如下所示:

new Thread(new Runnable() {
           @Override
           public void run() {
    // do your task here ...
}
}).start();

   這樣直接創建線程的方式壞處也是顯而易見的,當任務過多時,每個任務都創建一個線程,那麼大量CPU時間都浪費在線程的切換,而且線程過多時也是會佔用大量內存的,所以在實際使用時我們一般也不會採用這種方式(少量任務,新建一兩個線程也是可以的)。

 

    下面我們看下JDK提供的直接使用線程池的方案:

// 固定線程池 線程數量爲10
ExecutorService executorService = Executors.newFixedThreadPool(10);
    executorService.submit(new Runnable() {
                           @Override
                           public void run() {
        // do your task here ...
    }
});

    我們比較常用上述方式來做,那麼問題來了,這種方式有什麼不好的嘛?

 

2.阿里代碼規範關於線程池的描述

    在阿里的代碼規範中,有關於線程池的描述,如下

1. 線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

    直接來分析下這段代碼:

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

    // 關於LinkedBlockingQueue 的創建
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    // 可以看到容量基本是不限量,這樣當消費速度過慢時,數據會不斷的被寫入鏈表,內存有被耗盡的風險
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

3.Sentinel中關於線程池的使用

    Sentinel中有大量的線程池使用的情況,下面來截取一段代碼(ZookeeperDataSource.java)

// ZookeeperDataSource.java中,在成員變量中定義了一個線程池
    private final ExecutorService pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<Runnable>(1), new NamedThreadFactory("sentinel-zookeeper-ds-update"),
            new ThreadPoolExecutor.DiscardOldestPolicy());

// NamedThreadFactory定義
public class NamedThreadFactory implements ThreadFactory {

    private final ThreadGroup group;
    // 自增線程序列號
    private final AtomicInteger threadNumber = new AtomicInteger(1);

	// 可以自定義線程名稱
    private final String namePrefix;
	// 定義是否守護線程
    private final boolean daemon;

    public NamedThreadFactory(String namePrefix, boolean daemon) {
        this.daemon = daemon;
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        this.namePrefix = namePrefix;
    }

    public NamedThreadFactory(String namePrefix) {
        this(namePrefix, false);
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0);
        t.setDaemon(daemon);
        return t;
    }
}

    可以看到,Sentinel在創建線程池時

    1.首先自定義一個ThreadFactory,(Executors會使用默認的DefaultThreadFactory,兩個大同小異)。

 

    2.通過手動new 一個 ThreadPoolExecutor,自定義其中的參數來創建線程池,我們來分析下這個線程池的參數:

corePoolSize:1
maxPoolSize:1
workQueue:ArrayBlockQueue(1) // 長度爲1,通過控制這個長度,來放置內存溢出
rejectPolicy:discardOldestPolicy // 拋棄最先的一個任務

總結:

    以後我們在創建線程池的時候,儘量避免直接使用Executors來創建線程池。

    通過自定義一個ThreadPoolExecutor來創建線程池,自定義其中的參數即可。

 

參考:https://github.com/alibaba/Sentinel 

 

 

發佈了124 篇原創文章 · 獲贊 126 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章