眾所周知,Android中的主線程就是我們俗稱的UI線程,我們只能在主線程中操作UI,并且我們會避免在主線程中進(jìn)行耗時(shí)操作,這樣會造成主線程阻塞引起ANR,所以我們會把耗時(shí)操作(例如流的讀寫,下載文件等)放入我們新開辟的線程中進(jìn)行。
我不由得汪汪大笑..啊不,哈哈大笑,這還不簡單?請把我尊貴的Filco鍵盤給我,秀操作的時(shí)候到了,于是我敲下了這段大家都熟知的代碼~
new Thread(new Runnable() {
@Override
public void run() {
//你可以在這里盡情的做你愛做的事(操作UI除外)
}
}).start();
咱們這么寫確實(shí)可以達(dá)到我們想要的目的,但是這么寫是有弊端的,且聽我慢慢BB出來:
首先我們這么寫意味著我們使用了匿名內(nèi)部類,那就代表著我們沒辦法重用,也就是說我們這里新建的這個(gè)線程只能完成我們這里所指定的任務(wù)然后被銷毀回收,這樣對程序性能也有影響。我們下次還想進(jìn)行耗時(shí)操作的時(shí)候又得開啟新線程,這意味著我們不斷的在創(chuàng)建新線程對象和銷毀它,假如我們現(xiàn)在有1000個(gè)任務(wù)要在子線程中執(zhí)行,難道我們要循環(huán)創(chuàng)建1000次?1000個(gè)線程那得占用很大的系統(tǒng)資源,搞不好會OOM哦~
好了,那么我們這里就可以引入線程池的概念了。
什么是線程池?
顧名思義線程池就是一個(gè)池子里全是線程,啊對不起對不起...再也不敢了,這段去掉。
線程池是指在初始化一個(gè)多線程應(yīng)用程序過程中創(chuàng)建一個(gè)線程集合,然后在需要執(zhí)行新的任務(wù)時(shí)重用這些線程而不是新建一個(gè)線程。線程池中線程的數(shù)量通常完全取決于可用內(nèi)存數(shù)量和應(yīng)用程序的需求。然而,增加可用線程數(shù)量是可能的。線程池中的每個(gè)線程都有被分配一個(gè)任務(wù),一旦任務(wù)已經(jīng)完成了,線程回到池子中并等待下一次分配任務(wù)。
看了這段你懂了嗎?我們可以這么理解,有這么一個(gè)集合,里邊全是線程,不同類型的線程池會有不同的工作模式而已。
接下來我們來介紹下常用的4個(gè)線程池。
四種常用的線程池
fixedThreadPool
使用線程池中的fixedThreadPool,這種線程池的特點(diǎn)是在你創(chuàng)建這個(gè)線程池時(shí)會指定一個(gè)最大線程數(shù),
每提交一個(gè)任務(wù)就會新創(chuàng)建一個(gè)線程直到達(dá)到最大線程數(shù),如果已經(jīng)達(dá)到了最大線程數(shù)的話就會將新提交的任務(wù)緩存進(jìn)線程池隊(duì)列中等待空閑線程
注意:當(dāng)所有的任務(wù)都完成即線程空閑時(shí),這里的所有線程并不會自己釋放,會占用一定的系統(tǒng)資源,除非這整個(gè)線程池被關(guān)閉(即fixedThreadPool.shutdown();)
先來搞個(gè)例子
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);//這里指定最大線程數(shù)為3
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
Log.e("test", "線程編號:" + Thread.currentThread().getId() + "打印數(shù)字" + index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
運(yùn)行結(jié)果
08-01 14:07:13.751 21958-22058/com.example.android_1.testdemo E/test: 線程編號:3958打印數(shù)字1
08-01 14:07:13.751 21958-22059/com.example.android_1.testdemo E/test: 線程編號:3959打印數(shù)字2
08-01 14:07:13.751 21958-22057/com.example.android_1.testdemo E/test: 線程編號:3957打印數(shù)字0
08-01 14:07:15.753 21958-22059/com.example.android_1.testdemo E/test: 線程編號:3959打印數(shù)字4
08-01 14:07:15.753 21958-22058/com.example.android_1.testdemo E/test: 線程編號:3958打印數(shù)字3
08-01 14:07:15.754 21958-22057/com.example.android_1.testdemo E/test: 線程編號:3957打印數(shù)字5
08-01 14:07:17.757 21958-22059/com.example.android_1.testdemo E/test: 線程編號:3959打印數(shù)字7
08-01 14:07:17.757 21958-22057/com.example.android_1.testdemo E/test: 線程編號:3957打印數(shù)字6
08-01 14:07:17.757 21958-22058/com.example.android_1.testdemo E/test: 線程編號:3958打印數(shù)字8
08-01 14:07:19.758 21958-22057/com.example.android_1.testdemo E/test: 線程編號:3957打印數(shù)字9
看是不是至始至終都只有3個(gè)線程?
cachedThreadPool
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
}
});
這種線程池會根據(jù)需要,在線程可用時(shí),重用之前構(gòu)造好的池中線程。這個(gè)線程池在執(zhí)行 大量短生命周期的異步任務(wù)時(shí)(many short-lived asynchronous task),可以顯著提高程序性能。調(diào)用 execute 時(shí),可以重用之前已構(gòu)造的可用線程,如果不存在可用線程,那么會重新創(chuàng)建一個(gè)新的線程并將其加入到線程池中。如果線程超過 60 秒還未被使用,就會被中止并從緩存中移除。因此,線程池在長時(shí)間空閑后不會消耗任何資源。這個(gè)線程中是沒有限制線程數(shù)量的(其實(shí)還是有限制的,只不過數(shù)量為Interger. MAX_VALUE)
singleThreadExecutor
這種線程池會使用單個(gè)工作線程來執(zhí)行一個(gè)無邊界的隊(duì)列。(注意,如果單個(gè)線程在執(zhí)行過程中因?yàn)槟承╁e(cuò)誤中止,新的線程會替代它執(zhí)行后續(xù)線程)。它可以保證認(rèn)為是按順序執(zhí)行的,任何時(shí)候都不會有多于一個(gè)的任務(wù)處于活動(dòng)狀態(tài)。和 newFixedThreadPool(1) 的區(qū)別在于,如果線程遇到錯(cuò)誤中止,它是無法使用替代線程的。
通俗的來講你可以認(rèn)為你所提交的任務(wù)就是在排隊(duì),按順序來,我們敲個(gè)例子驗(yàn)證一下~
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
Runnable runnable0=new Runnable() {
@Override
public void run() {
try {
Log.e("test", "runnable0,線程ID"+Thread.currentThread().getId());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable runnable1=new Runnable() {
@Override
public void run() {
try {
Log.e("test", "runnable1,線程ID"+Thread.currentThread().getId());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable runnable2=new Runnable() {
@Override
public void run() {
try {
Log.e("test", "runnable2,線程ID"+Thread.currentThread().getId());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
singleThreadExecutor.execute(runnable0);
singleThreadExecutor.execute(runnable1);
singleThreadExecutor.execute(runnable2);
//特意設(shè)置了時(shí)間差異
看看運(yùn)行結(jié)果~
08-01 16:24:42.131 575-1082/com.example.android_1.testdemo E/test: runnable0,線程ID4124
08-01 16:24:45.134 575-1082/com.example.android_1.testdemo E/test: runnable1,線程ID4124
08-01 16:24:47.135 575-1082/com.example.android_1.testdemo E/test: runnable2,線程ID4124
可以看到是我們的預(yù)期結(jié)果~
newScheduledThreadPool
這種線程池也可以指定池中存在的最大線程數(shù),而且這種線程池能夠延時(shí)以及輪詢方式執(zhí)行任務(wù),這種線程池有這幾種核心方法,下面我們來一一看一番~
schedule(Runnable command, long delay, TimeUnit unit)
我們直接看代碼以及注釋
//延時(shí)處理Runnable任務(wù)
//參數(shù):schedule(需要執(zhí)行的任務(wù)Runnable,延時(shí)時(shí)間長度,延時(shí)時(shí)間單位)
//例如下面這段代碼的意思就是延時(shí)3秒執(zhí)行Runnable任務(wù)
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
}
}, 3, TimeUnit.SECONDS);
schedule(Callable<V> callable, long delay, TimeUnit unit)
這個(gè)和上邊那個(gè)差不多,只不過一個(gè)是Runnable任務(wù),一個(gè)是Callable任務(wù)
//延時(shí)處理Callable任務(wù)
//參數(shù):schedule(需要執(zhí)行的任務(wù)Runnable,延時(shí)時(shí)間長度,延時(shí)時(shí)間單位)
//例如下面這段代碼的意思就是延時(shí)3秒執(zhí)行Callable任務(wù)
scheduledExecutorService.schedule(new Callable<Object>() {
@Override
public Object call() {
return null;
}
}, 3, TimeUnit.SECONDS);
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
這個(gè)方法就是類似于輪詢,先來看一下參數(shù)的意思
scheduleAtFixedRate(需要執(zhí)行的Runnable任務(wù),第一次執(zhí)行任務(wù)所需要延遲的時(shí)間, 每一次開始執(zhí)行任務(wù)后與下一次執(zhí)行任務(wù)的延遲時(shí)間, 延時(shí)時(shí)間單位)
像下面這段代碼的意思就是延時(shí)3秒執(zhí)行第一次任務(wù),從任務(wù)開始的時(shí)候計(jì)時(shí),過了5秒再執(zhí)行下一次任務(wù)。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執(zhí)行:" + date);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", date);
}
}, 3, 5, TimeUnit.SECONDS);
我們來看下運(yùn)行結(jié)果
08-02 11:40:50.880 22738-22738/com.example.android_1.testdemo E/test: 開始執(zhí)行:2018-08-02 11:40:50
08-02 11:40:53.887 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:40:53
08-02 11:40:58.894 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:40:58
08-02 11:41:03.889 22738-22991/com.example.android_1.testdemo E/test: 2018-08-02 11:41:03
08-02 11:41:08.891 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:41:08
從結(jié)果我們可以看出,確實(shí)如我們所料,但是還有另外一種情況,如果我要執(zhí)行的任務(wù)所需要的時(shí)間大于間隔時(shí)間怎么辦?這個(gè)方法的做法是等前一個(gè)任務(wù)執(zhí)行完了再繼續(xù)下一個(gè)任務(wù),也就是說,時(shí)間間隔會與我們指定的時(shí)間不一樣,我們寫段代碼驗(yàn)證一下~
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執(zhí)行:" + date);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", date);
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3, 5, TimeUnit.SECONDS);
這段代碼我每個(gè)任務(wù)需要6s的執(zhí)行時(shí)間,大于5秒的間隔時(shí)間,來看看結(jié)果如何吧~
08-02 11:44:55.473 23286-23286/com.example.android_1.testdemo E/test: 開始執(zhí)行:2018-08-02 11:44:55
08-02 11:44:58.480 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:44:58
08-02 11:45:04.499 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:45:04
08-02 11:45:10.505 23286-23435/com.example.android_1.testdemo E/test: 2018-08-02 11:45:10
08-02 11:45:16.516 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:45:16
從結(jié)果我們可以看出...時(shí)間間隔變成了6秒,那是因?yàn)槲覀冎付ǖ?秒間隔過后,系統(tǒng)發(fā)現(xiàn)上一個(gè)任務(wù)還沒執(zhí)行完成,所以等上一個(gè)執(zhí)行完成了再繼續(xù)執(zhí)行下一個(gè)任務(wù),所以變成了6秒~
但是如果發(fā)現(xiàn)已經(jīng)執(zhí)行完了就會按我們規(guī)定的時(shí)間間隔來。
scheduleWithFixedDelay`(Runnable command, long initialDelay, long period, TimeUnit unit)
這個(gè)方法的參數(shù)意義和上一個(gè)是一樣的,不同之處在于這個(gè)方法是每次任務(wù)執(zhí)行結(jié)束之后再去計(jì)算延時(shí),而不是每次開始就計(jì)時(shí),我們來寫段代碼驗(yàn)證一下~
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執(zhí)行:" + date);
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", date);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3, 5, TimeUnit.SECONDS);
看一下運(yùn)行結(jié)果~
08-02 11:48:40.409 23741-23741/com.example.android_1.testdemo E/test: 開始執(zhí)行:2018-08-02 11:48:40
08-02 11:48:43.416 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:48:43
08-02 11:48:51.430 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:48:51
08-02 11:48:59.444 23741-23865/com.example.android_1.testdemo E/test: 2018-08-02 11:48:59
08-02 11:49:07.450 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:49:07
08-02 11:49:15.456 23741-23873/com.example.android_1.testdemo E/test: 2018-08-02 11:49:15
看,是不是間隔時(shí)間變成了任務(wù)執(zhí)行時(shí)間+指定間隔時(shí)間啦?這就和我們想的完全一樣~
這里小結(jié)一下:
scheduleAtFixedRate ,是以上一個(gè)任務(wù)開始的時(shí)間計(jì)時(shí),period(即第三個(gè)參數(shù))時(shí)間過去后,檢測上一個(gè)任務(wù)是否執(zhí)行完畢,如果上一個(gè)任務(wù)執(zhí)行完畢,則當(dāng)前任務(wù)立即執(zhí)行,如果上一個(gè)任務(wù)沒有執(zhí)行完畢,則需要等上一個(gè)任務(wù)執(zhí)行完畢后立即執(zhí)行。
scheduleWithFixedDelay,是以上一個(gè)任務(wù)結(jié)束時(shí)開始計(jì)時(shí),period(即第三個(gè)參數(shù))時(shí)間過去后,立即執(zhí)行。
最后
這就是我們比較常用的四大線程池,我們平時(shí)開發(fā)的時(shí)候可以根據(jù)應(yīng)用場景不同而選擇不同類型的線程池來使用嘻嘻