Android 多執行緒 執行緒池原理 封裝執行緒池
我自己理解看來。執行緒池顧名思義就是一個容器的意思,需要注意的是,每一個執行緒都是需要CPU分配資源去執行的。如果由於總是new Thread()開啟一個執行緒,那麼就會大量的消耗CPU的資源,導致Android執行變慢,甚至OOM(out
of memory),因而Java就出現了一個ThreadPoolExecutor來管理這些執行緒。控制最多的執行緒數maximumPoolSize,核心執行緒數corePoolSize,來管理我們需要開啟的執行緒數。這樣減少了建立和銷燬執行緒的次數,每個工作執行緒都可以被重複利用,可執行多個任務。所以,我們就可以根據手機的CPU核數來控制App可以開的最大執行緒數,保證程式的合理執行。
在Android中當同時併發多個網路執行緒時,引入執行緒池技術會極大地提高APP的效能。例如:多執行緒下載,點一個下載一個(假設允許最多同時下載五個),當點到第六個的時候開始等待,這就涉及到執行緒的管理。
Android引入執行緒池好處是:提升了效能(建立和消耗物件費時費CPU資源);防止記憶體過度消耗(控制活動執行緒的數量,防止併發執行緒過多)。
1.ThreadPoolExecutor
1.ThreadPoolExecutor引數
jdk自身帶有執行緒池的實現類ThreadPoolExecutor,使用ThreadPoolExecutor,瞭解其每個引數的意義是必不可少的。
ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)
corePoolSize: 核心執行緒數,能夠同時執行的任務數量;
maximumPoolSize:除去緩衝佇列中等待的任務,最大能容納的任務數(其實是包括了核心執行緒池數量);
keepAliveTime:超出workQueue的等待任務的存活時間,就是指maximumPoolSize裡面的等待任務的存活時間;
unit:時間單位;
workQueue:阻塞等待執行緒的佇列,一般使用new LinkedBlockingQueue()這個,如果不指定容量,會一直往裡邊新增,沒有限制,workQueue永遠不會滿;
threadFactory:建立執行緒的工廠,使用系統預設的類;
handler:當任務數超過maximumPoolSize時,對任務的處理策略,預設策略是拒絕新增;
2.阻塞佇列
佇列用於排隊,避免一瞬間出現大量請求的問題。
阻塞佇列分為 有限佇列(SynchronousQueue、ArrayBlockingQueue)和 無限佇列(LinkedBloackingQueue)。
3.執行流程
當執行緒數小於corePoolSize時,每新增一個任務,則立即開啟執行緒執行;當corePoolSize滿的時候,後面新增的任務將放入緩衝佇列workQueue等待;當workQueue也滿的時候,看是否超過maximumPoolSize執行緒數,如果超過,預設拒絕執行。
下面我們看個例子:假如corePoolSize=2,maximumPoolSize=3,workQueue容量為8;最開始,執行的任務A,B,此時corePoolSize已用完,再次執行任務C,則C將被放入緩衝佇列workQueue中等待著,如果後來又添加了7個任務,此時workQueue已滿,則後面再來的任務將會和maximumPoolSize比較,由於maximumPoolSize為3,所以只能容納1個了,因為有2個在corePoolSize中運行了,所以後面來的任務預設都會被拒絕。
4.終止一個執行緒
我們編寫終止一個執行緒的 junit 測試方法:
private class MyRunnable implements Runnable {
public volatile boolean flag = true;
@Override
public void run() {
while (flag && !Thread.interrupted()) {
try {
System.out.println("running");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
return; //2.中斷執行緒時, 注意此處必須做處理
}
}
}
}
@Test
public void RunnableTest() throws InterruptedException {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(2000);
runnable.flag = true;
thread.interrupt(); //1.中斷執行緒
}
2.封裝執行緒池管理者
下面我們基於ThreadPoolExecutor對執行緒池進行封裝:
/**
* 執行緒池管理 管理整個專案中所有的執行緒,所以不能有多個例項物件
*/
public class ThreadPoolManager {
/**
* 單例設計模式(餓漢式)
* 單例首先私有化構造方法,然後餓漢式一開始就開始建立,並提供get方法
*/
private static ThreadPoolManager mInstance = new ThreadPoolManager();
public static ThreadPoolManager getInstance() {
return mInstance;
}
private int corePoolSize;//核心執行緒池的數量,同時能夠執行的執行緒數量
private int maximumPoolSize;//最大執行緒池數量,表示當緩衝佇列滿的時候能繼續容納的等待任務的數量
private long keepAliveTime = 1;//存活時間
private TimeUnit unit = TimeUnit.HOURS;
private ThreadPoolExecutor executor;
private ThreadPoolManager() {
/**
* 給corePoolSize賦值:當前裝置可用處理器核心數*2 + 1,能夠讓cpu的效率得到最大程度執行(有研究論證的)
*/
corePoolSize = Runtime.getRuntime().availableProcessors()*2+1;
maximumPoolSize = corePoolSize; //雖然maximumPoolSize用不到,但是需要賦值,否則報錯
executor = new ThreadPoolExecutor(
corePoolSize, //當某個核心任務執行完畢,會依次從緩衝佇列中取出等待任務
maximumPoolSize, //5,先corePoolSize,然後new LinkedBlockingQueue<Runnable>(),然後maximumPoolSize,但是它的數量是包含了corePoolSize的
keepAliveTime, //表示的是maximumPoolSize當中等待任務的存活時間
unit,
new LinkedBlockingQueue<Runnable>(), //緩衝佇列,用於存放等待任務,Linked的先進先出
Executors.defaultThreadFactory(), //建立執行緒的工廠
new ThreadPoolExecutor.AbortPolicy() //用來對超出maximumPoolSize的任務的處理策略
);
}
/**
* 執行任務
*/
public void execute(Runnable runnable){
if(runnable==null)return;
executor.execute(runnable);
}
/**
* 從執行緒池中移除任務
*/
public void remove(Runnable runnable){
if(runnable==null)return;
executor.remove(runnable);
}
}
其中,獲取當前可用的處理器核心數我們用Runtime.getRuntime().availableProcessors(),我們來看它的註釋:下面我們來看一下Runnable與執行緒的區別:
Runnable只是一個介面,它的原始碼如下,而執行緒是真正開啟系統資源去執行任務,他們兩個,執行緒是真正消耗系統資源的
/**
* Represents a command that can be executed. Often used to run code in a
* different {@link Thread}.
*/
public interface Runnable {
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
到這裡已經基本上介紹完了執行緒池,下面我們通過實際編碼看一下如何使用執行緒池。
3.演示執行緒池的使用方法
下面是一個執行緒池的例子(演示多執行緒執行任務),以加深對原理的理解
1.引入我們封裝好的ThreadPoolManager.java
2.演示功能
/**
* 演示執行緒池
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 建立九個任務
*/
for (int i = 0; i < 9; i++) {
ThreadPoolManager.getInstance().execute(new DownloadTask(i));
}
}
/**
* 模仿下載任務,實現Runnable
*/
class DownloadTask implements Runnable{
private int num;
public DownloadTask(int num) {
super();
this.num = num;
Log.d("JAVA", "task - "+num + " 等待中...");
}
@Override
public void run() {
Log.d("JAVA", "task - "+num + " 開始執行了...開始執行了...");
SystemClock.sleep(5000); //模擬延時執行的時間
Log.e("JAVA", "task - "+num + " 結束了...");
}
}
}
列印結果如下: