About ExecutorService(2)裁蚁,自定義線程池

About ExecutorService(1),F(xiàn)uture&FutureTask

About ExecutorService(2)继准,自定義線程池

About ExecutorService(3)枉证,我所認識的AsyncTask

About ExecutorService(4),AsyncTask番外篇

琢磨了一下移必,還是把這篇提前了室谚,本片篇幅可能會有些長,甚至冗余,請各位看官原諒我這拙劣的寫作能力秒赤。

本篇的重點是猪瞬,簡單線程池的實現(xiàn),Executor框架入篮,優(yōu)化后的自定義線程池陈瘦。

Android主線程不是線程安全的,不能阻塞太久潮售,否則會報ANR異常痊项,于是,我們經(jīng)常這樣做來處理耗時操作酥诽,查詢數(shù)據(jù)线婚,甚至聯(lián)網(wǎng)請求。

    new Thread(new Runnable() {
        @Override
        public void run() {

            /*處理耗時操作*/
        }
    }).start();

這段看似常用盆均,而且大家都在用,其實蘊藏著很多學問漱逸。那么我就為大家解釋一下泪姨。

這段代碼首先創(chuàng)建了一個線程,并在run( )方法結(jié)束后饰抒,系統(tǒng)自動回收該線程肮砾,可以說在簡單的應用中,沒什么問題袋坑,但是如果放到復雜的生產(chǎn)環(huán)境中仗处,系統(tǒng)由于真實環(huán)境的需要,可能會開啟很多線程來做支撐枣宫。而當線程數(shù)量過大時婆誓,反而會消耗CUP和內(nèi)存資源。

與進程相比也颤,線程算的上是一個輕量級的工具洋幻,但是!3崛ⅰ文留!其創(chuàng)建和關閉都需要花費時間和資源,頻繁的創(chuàng)建子線程竭沫,很可能出現(xiàn)造成創(chuàng)建和銷毀線程所花的時間要比業(yè)務邏輯的工作時間還要長的悲劇燥翅,這樣就得不償失了。

Android的垃圾回收機制是基于虛擬機的蜕提,雖智能森书,但不夠完美,子線程頻繁創(chuàng)建和銷長毀,依然會搶奪資源拄氯,不僅給GC帶來壓力躲查,嚴重的時候甚至會報OOM異常。

所以译柏,應該對子線程的使用掌握一個度镣煮,接下來,duang鄙麦,duang典唇,duang,給大家?guī)砭€程池的簡單實現(xiàn)胯府。

線程池的基本功能就是進行線程的復用介衔,由于篇幅原因,我把它提前發(fā)布出來并附上鏈接骂因。

線程池的簡單實現(xiàn)

通過上面鏈接的小例子炎咖,是不是對線程池的構造有了一個初步的了解。接下來為大家介紹一下系統(tǒng)自帶的Executor框架寒波,為開發(fā)者們自定義線程池帶來了極大的方便乘盼。

可以說Executors是一個工廠類,里面有許多靜態(tài)方法俄烁,供開發(fā)者調(diào)用绸栅。

    /*該方法返回一個固定線程數(shù)量的線程池,該線程池池中的線程數(shù)量始終不變页屠。當有一個新的任務提交時粹胯,線程池中若有空閑線程,則立即執(zhí)行辰企。
    * 若沒有风纠,則新的任務會被暫存在一個任務隊列中,待有線程空閑時蟆豫,便處理在任務隊列中的任務
    * 默認等待隊列長度為Integer.MAX_VALUE*/
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);


    /*該方法返回一個只有一個線程的線程池议忽。
    * 若多余一個任務被提交到線程池,任務會被保存在一個任務隊列中十减,等待線程空閑栈幸,按先入先出順序執(zhí)行隊列中的任務
    * 默認等待隊列長度為Integer.MAX_VALUE*/
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();


    /*該方法返回一個可根據(jù)實際情況調(diào)整線程數(shù)量的線程池。
    * 線程池的線程數(shù)量不確定帮辟,但若有空閑線程可以復用速址,則會優(yōu)先使用可復用的線程。
    * 若所有線程均在工作由驹,又有新任務的提交芍锚,則會創(chuàng)建新的線程處理任務昔园。
    * 所有線程在當前任務執(zhí)行完畢后,將返回線程池進行復用*/
    ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();


    /*該方法返回一個ScheduledExecutorService對象并炮,線程池大小為1默刚。
    * ScheduledExecutorService接口在ExecutorService接口之上擴展了在給定時間內(nèi)執(zhí)行某任務的功能,
    * 如在某個固定的延時之后執(zhí)行逃魄,或者周期性執(zhí)行某個任務*/
    ExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();


    /*該方法也返回一個ScheduledExecutorService對象荤西,但該線程池可以指定線程數(shù)量*/
    ExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1);

如果童鞋們認為,Executors工廠類提供的自定義線程池伍俘,還不夠用的話邪锌,也可以自定義線程池。通過ThreadPoolExecutor的構造函數(shù)自定義需要的線程池癌瘾。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

corePoolSize:當提交一個任務到線程池時觅丰,線程池會創(chuàng)建一個線程來執(zhí)行任務,即使其他空閑的基本線程能夠執(zhí)行新任務也會創(chuàng)建線程妨退,等到需要執(zhí)行的任務數(shù)大于線程池基本大小時就不再創(chuàng)建妇萄。如果調(diào)用了線程池的prestartAllCoreThreads方法,線程池會提前創(chuàng)建并啟動所有基本線程咬荷。

maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)嚣伐。如果隊列滿了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)萍丐,則線程池會再創(chuàng)建新的線程執(zhí)行任務。值得注意的是如果使用了無界的任務隊列這個參數(shù)就沒什么效果放典。

keepAliveTime:當線程池線程數(shù)量超過corePoolSize時逝变,多余的空余線程存活時間,即奋构,超過corePoolSize的空閑線程壳影,在多長時間內(nèi)被銷毀。如果任務很多弥臼,并且每個任務執(zhí)行的時間比較短宴咧,可以調(diào)大這個時間,提高線程的利用率径缅。

unit:keepAliveTime的單位掺栅。

workQueue:任務隊列,被提交但尚未被執(zhí)行任務的任務纳猪。有以下幾種隊列模式:

  1. ArrayBlockingQueue:是一個基于數(shù)組結(jié)構的有界阻塞隊列氧卧,此隊列按 FIFO(先進先出)原則對元素進行排序。

  2. LinkedBlockingQueue:一個基于鏈表結(jié)構的阻塞隊列氏堤,此隊列按FIFO (先進先出) 排序元素沙绝,吞吐量通常要高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個隊列。

  3. SynchronousQueue:一個不存儲元素的阻塞隊列闪檬。每個插入操作必須等到另一個線程調(diào)用移除操作星著,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue粗悯,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個隊列虚循。

  4. PriorityBlockingQueue:一個具有優(yōu)先級得無限阻塞隊列。

threadFactory:線程工廠为黎,用于創(chuàng)建線程邮丰,一般用默認的即可。也可以通過線程工廠給每個創(chuàng)建出來的線程設置更有意義的名字铭乾,Debug和定位問題時非常有幫助剪廉。

handler:拒絕策略,當隊列和線程池都滿了炕檩,說明線程池處于飽和狀態(tài)斗蒋,那么必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy笛质,表示無法處理新任務時拋出異常泉沾。系統(tǒng)內(nèi)置的策略模式如下:

  1. AbortPolicy:直接拋出異常,阻止系統(tǒng)正常工作妇押。

  2. CallerRunsPolicy:只要線程未關閉跷究,該策略直接在調(diào)用者線程中,運行當前被丟棄的任務敲霍。

  3. DiscardOldestPolicy:該策略將丟棄最老的一個請求俊马,也就是即將被執(zhí)行的一個人任務,并嘗試再次提交當前任務肩杈。

  4. DiscardPolicy:該策略默默的丟棄無法處理對

  5. 當然也可以根據(jù)應用場景需要來實現(xiàn)RejectedExecutionHandler接口自定義策略柴我。

好像好久沒說到AT了,那么接下來我們根據(jù)源碼和畫圖的方式分析一下AT中到底構造了一個如何的線程池

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

為了保持處理器達到期望的使用率扩然,最優(yōu)線程池大小艘儒,終于在一本叫做《Java并發(fā)編程實踐》的書中找到了估算線程池大小的經(jīng)驗值:Nthread。

Nthread = Ncpu * Ucpu * (1 + W/C)

其中夫偶,Ncpu = CPU的數(shù)量界睁,Ucpu = 目標CPU的使用量,0 <= Ucpu <=1兵拢,W/C = 等待時間與計算時間的比率晕窑。

由于各種八核手機出現(xiàn),以及硬件的提升卵佛,這個公式貌似也不常用了杨赤。大家了解即可敞斋。

接下來介紹AT中線程池的工作原理

1,假如我手中用了雙核手機(兩個CPU)疾牲,初始狀態(tài)下線程池的工作線程為0植捎,當?shù)谝粋€任務進來時,會創(chuàng)建工作線程阳柔。則根據(jù)AT中計算CPU_COUNT = 2焰枢,CORE_POOL_SIZE = 3 ,紅色代表新任務舌剂,當線程池中的工作線程池數(shù)量已經(jīng)達到了CORE_POOL_SIZE济锄,下個任務,如果工作線程沒有空閑的話霍转,必將進入緩沖隊列中荐绝,進行排隊。

2避消,線程池中沒有空閑線程低滩,新的任務要進入緩沖隊列進行排隊。


3岩喷,緩沖隊列也滿了恕沫,線程池中的工作線程依然沒有空閑。

通過AT中的計算纱意,MAXIMUM_POOL_SIZE = 5婶溯。

當線程池工作線程達到CORE_POOL_SIZE并且沒有空閑,緩沖隊列任務數(shù)量達到AT中設定的128偷霉,

此時新的任務進入線程池之前爬虱,如果線程池中工作線程數(shù)量小于MAXIMUM_POOL_SIZE,則創(chuàng)建新的工作線程執(zhí)行任務腾它。如下圖,線程池創(chuàng)建第4個工作線程死讹,執(zhí)行第129個任務瞒滴。

4.線程池工作線程數(shù)量等于MAXIMUM_POOL_SIZE,緩沖隊列數(shù)量等于最大workQuene最大值赞警,并且沒有工作線程處于空閑妓忍,第131任務進入線程池的時候,拒絕策略handler發(fā)揮作用愧旦,AT中默認的拒絕策略為拋出RuntimeException異常世剖。(RuntimeException和Error同屬于UncaughtException,可以在Application中進行捕獲和處理)

由上述步驟笤虫,不難發(fā)現(xiàn)workQueue應該避免無邊界隊列旁瘫,盡量使用帶邊界的祖凫,如ArrayBlockingQueue和固定capacity容量的LinkedBlockingQueue。否則線程池的緩沖隊列將是一個無邊界的隊列酬凳,任務過多而不能執(zhí)行拒絕策略的情況下惠况,可能會撐滿內(nèi)存,導致整個系統(tǒng)不可用宁仔,造成不可預估的后果稠屠。

片尾Tip:

最近有群里的朋友抱怨,Android studio 編譯速度太慢翎苫,太卡了权埠,交給大家一個小技巧,我的電腦還是公司的老款戴爾煎谍,至于你信不信攘蔽,反正我是信了,它確實快了粱快。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秩彤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子事哭,更是在濱河造成了極大的恐慌漫雷,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳍咱,死亡現(xiàn)場離奇詭異降盹,居然都是意外死亡,警方通過查閱死者的電腦和手機谤辜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門蓄坏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丑念,你說我怎么就攤上這事涡戳。” “怎么了脯倚?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵渔彰,是天一觀的道長。 經(jīng)常有香客問我推正,道長恍涂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任植榕,我火速辦了婚禮再沧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尊残。我一直安慰自己炒瘸,他們只是感情好淤堵,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著什燕,像睡著了一般粘勒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屎即,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天庙睡,我揣著相機與錄音,去河邊找鬼技俐。 笑死乘陪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的雕擂。 我是一名探鬼主播啡邑,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼井赌!你這毒婦竟也來了谤逼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仇穗,失蹤者是張志新(化名)和其女友劉穎流部,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纹坐,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡枝冀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了耘子。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片果漾。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谷誓,靈堂內(nèi)的尸體忽然破棺而出绒障,到底是詐尸還是另有隱情,我是刑警寧澤捍歪,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布户辱,位于F島的核電站,受9級特大地震影響费封,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒋伦,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一弓摘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痕届,春花似錦韧献、人聲如沸末患。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽璧针。三九已至,卻和暖如春渊啰,著一層夾襖步出監(jiān)牢的瞬間探橱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工绘证, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隧膏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓嚷那,卻偏偏與公主長得像胞枕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魏宽,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接:http://blog.csdn.net/u010687392/article/details/4985...
    xpengb閱讀 1,328評論 0 1
  • 前段時間遇到這樣一個問題腐泻,有人問微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時間較短,比如說一下上傳9張圖片,...
    加油碼農(nóng)閱讀 1,204評論 0 2
  • 前言 線程池是Java中的一個重要概念队询,從Android上來說派桩,當我們跟服務端進行數(shù)據(jù)交互的時候我們都知道主線程不...
    老實任閱讀 1,260評論 1 9
  • 轉(zhuǎn)眼,又到一年歲末娘摔! 突然想起三十年前的一段往事窄坦。 一九八七年底,有天我小病初愈去上學凳寺。那年我讀初三鸭津! 一進教室還...
    暖香閣主閱讀 190評論 0 1
  • 連續(xù)靈修115天【經(jīng)文】他高興遵行耶和華的道,并且從猶大除掉一切邱壇和木偶肠缨。 (歷代志下 17:6 和合本)【感動...
    喜樂付閱讀 6,183評論 0 0