1. 程式人生 > >Android 多執行緒 執行緒池原理 封裝執行緒池

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 + " 結束了...");
        }
    }
}
列印結果如下: