衆所周知,Android中的主線程就是我們俗稱的UI線程,我們隻能在主線程中操作UI,并且我們會避免在主線程中進行耗時操作,這樣會造成主線程阻塞引起ANR,是以我們會把耗時操作(例如流的讀寫,下載下傳檔案等)放入我們新開辟的線程中進行。
我不由得汪汪大笑..啊不,哈哈大笑,這還不簡單?請把我尊貴的Filco鍵盤給我,秀操作的時候到了,于是我敲下了這段大家都熟知的代碼~
new Thread(new Runnable() {
@Override
public void run() {
//你可以在這裡盡情的做你愛做的事(操作UI除外)
}
}).start();
咱們這麼寫确實可以達到我們想要的目的,但是這麼寫是有弊端的,且聽我慢慢BB出來:
首先我們這麼寫意味着我們使用了匿名内部類,那就代表着我們沒辦法重用,也就是說我們這裡建立的這個線程隻能完成我們這裡所指定的任務然後被銷毀回收,這樣對程式性能也有影響。我們下次還想進行耗時操作的時候又得開啟新線程,這意味着我們不斷的在建立新線程對象和銷毀它,假如我們現在有1000個任務要在子線程中執行,難道我們要循環建立1000次?1000個線程那得占用很大的系統資源,搞不好會OOM哦~
好了,那麼我們這裡就可以引入線程池的概念了。
什麼是線程池?
顧名思義線程池就是一個池子裡全是線程,啊對不起對不起...再也不敢了,這段去掉。
線程池是指在初始化一個多線程應用程式過程中建立一個線程集合,然後在需要執行新的任務時重用這些線程而不是建立一個線程。線程池中線程的數量通常完全取決于可用記憶體數量和應用程式的需求。然而,增加可用線程數量是可能的。線程池中的每個線程都有被配置設定一個任務,一旦任務已經完成了,線程回到池子中并等待下一次配置設定任務。
看了這段你懂了嗎?我們可以這麼了解,有這麼一個集合,裡邊全是線程,不同類型的線程池會有不同的工作模式而已。
接下來我們來介紹下常用的4個線程池。
四種常用的線程池
fixedThreadPool
使用線程池中的fixedThreadPool,這種線程池的特點是在你建立這個線程池時會指定一個最大線程數,
每送出一個任務就會新建立一個線程直到達到最大線程數,如果已經達到了最大線程數的話就會将新送出的任務緩存進線程池隊列中等待空閑線程
注意:當所有的任務都完成即線程空閑時,這裡的所有線程并不會自己釋放,會占用一定的系統資源,除非這整個線程池被關閉(即fixedThreadPool.shutdown();)
先來搞個例子
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);//這裡指定最大線程數為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() + "列印數字" + index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
運作結果
08-01 14:07:13.751 21958-22058/com.example.android_1.testdemo E/test: 線程編号:3958列印數字1
08-01 14:07:13.751 21958-22059/com.example.android_1.testdemo E/test: 線程編号:3959列印數字2
08-01 14:07:13.751 21958-22057/com.example.android_1.testdemo E/test: 線程編号:3957列印數字0
08-01 14:07:15.753 21958-22059/com.example.android_1.testdemo E/test: 線程編号:3959列印數字4
08-01 14:07:15.753 21958-22058/com.example.android_1.testdemo E/test: 線程編号:3958列印數字3
08-01 14:07:15.754 21958-22057/com.example.android_1.testdemo E/test: 線程編号:3957列印數字5
08-01 14:07:17.757 21958-22059/com.example.android_1.testdemo E/test: 線程編号:3959列印數字7
08-01 14:07:17.757 21958-22057/com.example.android_1.testdemo E/test: 線程編号:3957列印數字6
08-01 14:07:17.757 21958-22058/com.example.android_1.testdemo E/test: 線程編号:3958列印數字8
08-01 14:07:19.758 21958-22057/com.example.android_1.testdemo E/test: 線程編号:3957列印數字9
看是不是至始至終都隻有3個線程?
cachedThreadPool
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
}
});
這種線程池會根據需要,線上程可用時,重用之前構造好的池中線程。這個線程池在執行 大量短生命周期的異步任務時(many short-lived asynchronous task),可以顯著提高程式性能。調用 execute 時,可以重用之前已構造的可用線程,如果不存在可用線程,那麼會重新建立一個新的線程并将其加入到線程池中。如果線程超過 60 秒還未被使用,就會被中止并從緩存中移除。是以,線程池在長時間空閑後不會消耗任何資源。這個線程中是沒有限制線程數量的(其實還是有限制的,隻不過數量為Interger. MAX_VALUE)
singleThreadExecutor
這種線程池會使用單個工作線程來執行一個無邊界的隊列。(注意,如果單個線程在執行過程中因為某些錯誤中止,新的線程會替代它執行後續線程)。它可以保證認為是按順序執行的,任何時候都不會有多于一個的任務處于活動狀态。和 newFixedThreadPool(1) 的差別在于,如果線程遇到錯誤中止,它是無法使用替代線程的。
通俗的來講你可以認為你所送出的任務就是在排隊,按順序來,我們敲個例子驗證一下~
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);
//特意設定了時間差異
看看運作結果~
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
可以看到是我們的預期結果~
newScheduledThreadPool
這種線程池也可以指定池中存在的最大線程數,而且這種線程池能夠延時以及輪詢方式執行任務,這種線程池有這幾種核心方法,下面我們來一一看一番~
schedule(Runnable command, long delay, TimeUnit unit)
我們直接看代碼以及注釋
//延時處理Runnable任務
//參數:schedule(需要執行的任務Runnable,延時時間長度,延時時間機關)
//例如下面這段代碼的意思就是延時3秒執行Runnable任務
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
}
}, 3, TimeUnit.SECONDS);
schedule(Callable callable, long delay, TimeUnit unit)
這個和上邊那個差不多,隻不過一個是Runnable任務,一個是Callable任務
//延時處理Callable任務
//參數:schedule(需要執行的任務Runnable,延時時間長度,延時時間機關)
//例如下面這段代碼的意思就是延時3秒執行Callable任務
scheduledExecutorService.schedule(new Callable<Object>() {
@Override
public Object call() {
return null;
}
}, 3, TimeUnit.SECONDS);
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
這個方法就是類似于輪詢,先來看一下參數的意思
scheduleAtFixedRate(需要執行的Runnable任務,第一次執行任務所需要延遲的時間, 每一次開始執行任務後與下一次執行任務的延遲時間, 延時時間機關)
像下面這段代碼的意思就是延時3秒執行第一次任務,從任務開始的時候計時,過了5秒再執行下一次任務。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執行:" + 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);
我們來看下運作結果
08-02 11:40:50.880 22738-22738/com.example.android_1.testdemo E/test: 開始執行: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
從結果我們可以看出,确實如我們所料,但是還有另外一種情況,如果我要執行的任務所需要的時間大于間隔時間怎麼辦?這個方法的做法是等前一個任務執行完了再繼續下一個任務,也就是說,時間間隔會與我們指定的時間不一樣,我們寫段代碼驗證一下~
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執行:" + 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);
這段代碼我每個任務需要6s的執行時間,大于5秒的間隔時間,來看看結果如何吧~
08-02 11:44:55.473 23286-23286/com.example.android_1.testdemo E/test: 開始執行: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
從結果我們可以看出...時間間隔變成了6秒,那是因為我們指定的5秒間隔過後,系統發現上一個任務還沒執行完成,是以等上一個執行完成了再繼續執行下一個任務,是以變成了6秒~
但是如果發現已經執行完了就會按我們規定的時間間隔來。
scheduleWithFixedDelay`(Runnable command, long initialDelay, long period, TimeUnit unit)
這個方法的參數意義和上一個是一樣的,不同之處在于這個方法是每次任務執行結束之後再去計算延時,而不是每次開始就計時,我們來寫段代碼驗證一下~
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
Log.e("test", "開始執行:" + 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);
看一下運作結果~
08-02 11:48:40.409 23741-23741/com.example.android_1.testdemo E/test: 開始執行: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
看,是不是間隔時間變成了任務執行時間+指定間隔時間啦?這就和我們想的完全一樣~
這裡小結一下:
scheduleAtFixedRate ,是以上一個任務開始的時間計時,period(即第三個參數)時間過去後,檢測上一個任務是否執行完畢,如果上一個任務執行完畢,則目前任務立即執行,如果上一個任務沒有執行完畢,則需要等上一個任務執行完畢後立即執行。
scheduleWithFixedDelay,是以上一個任務結束時開始計時,period(即第三個參數)時間過去後,立即執行。
最後
這就是我們比較常用的四大線程池,我們平時開發的時候可以根據應用場景不同而選擇不同類型的線程池來使用~嘻嘻~