從線程池模型理解線程池的工作原理

首先要知道線程池是用來幹嘛的,線程池是通過複用線程達到最大利用資源的

線程池的關鍵參數

線程池的構造方法如下

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

簡單解釋一下:
corePoolSize : 核心線程數,默認情況下不會退出;
maximumPoolSize: 最大線程數,這個數字是包括核心線程數的,最大線程數 = 核心線程數+非核心線程數;
long keepAliveTime:線程保持存活的時間,核心線程也可以設置,但是默認情況下不會退出;非核心線程數這個參數默認生效;
TimeUnit unit :keepAliveTime 的時間單位,時分秒等;
workQueue : 工作隊列,是個阻塞隊列;
threadFactory : 線程工廠,通常用來設置線程的名字和優先級等;
handler :當線程池處理不了提交的任務時,需要告訴線程池要怎麼處理,例如拋異常等。

線程池模型

網上有很多講解線程池的圖片、代碼講解,但是看起來依然很費力,我把源碼大體線程複用流程寫了個僞代碼(核心線程不停的默認情況),可以對比源碼理思路,這裏不貼了:

void execute(Runnable r){
    if(當前線程數<core ){
        New Thread(new Runnable(){
                Public void run(){
                       While(r!=null || r = takeFromQueue()!= null){
                                r.run(); 
                                r = null;  
                        }
                  }
            })
}else if(!queue.isFull()){
    queque.addTask(r);
}else if(當前線程數<max ){
        New Thread(new Runnbale(){
            Public void run(){
                    While(r != null || r = pollAllowTimeout(超時時間)){
                            r.run();
                            R = null;
                    }
            }
         })
    }else{
            拒絕策略;
        }
}

僞代碼中線程裏面的操作很像,但是爲了好理解分開寫了。執行順序:
1、如果有任務來了,沒有到達核心線程數,會立即新建一個線程執行,並且線程生命週期不會結束(terminal狀態),因爲run()方法沒有走完,這個任務執行完後(r = null),線程會繼續從阻塞隊列裏拿任務,拿不到會一直阻塞(take),默認不會停。
2、超過核心數,但是阻塞隊列沒有滿,會把任務放在阻塞隊列裏;
3、阻塞隊列滿了,但是沒有達到最大核心數,會新建一個線程,立馬執行這個任務,執行完後從隊列取任務,但是這時候是poll,是有超時退出的取,這個超時時間就是keepAliveTime。
5、隊列滿了,線程數也達到最大值了,線程池不知道怎麼處理,用handler的策略來拒絕任務。

線程池的原理

1、根據上面分析,核心線程默認不會停的,線程的生命週期在run()方法執行完結束,首先會在while中循環,提交的任務執行完後還會從隊列取,即使隊列爲空,也會阻塞在那等待;–複用線程
2、核心任務(沒達到核心線程數時提交的任務)是不會加到隊列裏的,非核心任務纔會加到隊列裏;用隊列控制吞吐,是常用的設計思想之一。–控制吞吐

再來看看我們常用的幾種線程池:
CachedThreadPool :

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

核心線程數爲0,最大線程數無界(Integer.MAX_VALUE),60s後非核心線程退出,使用的隊列是同步隊列SynchronousQueue,SynchronousQueue是個無容量隊列,所以不能通過peek方法獲取頭部元素;也不能單獨插入元素,put和take是成對出現的,put一個元素,一定要等另一端take掉。所以CachedThreadPool一旦有任務,會立即新建一個非核心線程執行,由於是線程數無上限,所以一旦任務多,就會不停地新建線程,60s後還沒執行完,就會超時退出。另一個角度,任務在小於且接近60s的時間裏結束,那麼可能會有大量的線程在同時進行,佔用CPU,這種情況總結就是**“提交速度大於任務執行速度”,所以CachedThreadPool適合提交頻繁,且執行時間短的任務**。
FixedThreadPool:

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

核心線程數和最大線程數是一樣的,也就是隻有n個核心線程,而LinkedBlockingQueue是無界的隊列(容量爲Integer.MAX_VALUE),也就是不會滿,也就不會產生非核心線程數,第一個任務結束後,核心線程會一直從隊列裏取任務不退出。
SingleThreadExecutor:

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

核心線程數和最大線程數都是1,是FixedThreadPool的特例(n = 1),不同的是,由於只有一個核心線程,保證了所有任務都是在一個線程裏有序執行。

Android線程池和內存優化

線程池和內存有什麼關係?
1、核心線程的鍋。由於核心線程默認不退出的特點,核心線程指向的對象一直都是運行中的,如果核心線程開的很大,那麼這些線程對象在線程池使用shutdown之前,都是佔內存的。
再看下面一個例子:

Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(@NonNull final Runnable r) {
                new Thread(){
                    @Override
                    public void run() {
                        r.run();
                    }
                };
                return null;
            }
        });

factory裏的線程是匿名內部類,一旦這段代碼放在Activity裏,那麼Thread對象會持有Activity的引用,而且是個強引用,那麼外部的activity就一定會內存泄漏。
解決辦法:

  1. ThreadFactory 通過聲明爲靜態內部類;
  2. 如果在Activity中的線程池,有必要的話請考慮在onDestory的時候線程池shutdown。

2、阻塞隊列的鍋。像SingleThreadPool和FixThreadPool這兩種線程池,隊列大小是無界的,一旦線程處理的速度慢,而任務提交的快,就會有大量的任務堆積在隊列裏,從而在執行完之前造成一段時間的內存泄漏。
解決辦法:合理設置線程池大小。

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