線程池算是Android開發中非常常用的一個東西了,只要涉及到線程的地方,大多數情況下都會涉及到線程池。Android開發中線程池的使用和Java中線程池的使用基本一致。那么今天我想來總結一下Android開發中線程池的使用。
假如說要做一個ListView,ListView上有一個item,每個item上都有一張圖片需要從網絡上加載,如果不使用線程池,簡單的通過下面的方式來開啟一個新線程:
new Thread(new Runnable() {
@Override
public void run() {
//網絡訪問
}
}).start();
這種用法主要存在以下3點問題:
1.針對每一個item都創建一個新線程,這樣會導致頻繁的創建線程,線程執行完之后又被回收,又會導致頻繁的GC
2.這么多線程缺乏統一管理,各線程之間互相競爭,降低程序的運行效率,手機頁面卡頓,甚至會導致程序崩潰
3.如果一個item滑出頁面,則要停止該item上圖片的加載,但是如果使用這種方式來創建線程,則無法實現線程停止執行
如果使用線程池,我們就可以很好的解決以上三個問題:
1.重用已經創建的好的線程,避免頻繁創建進而導致的頻繁GC
2.控制線程并發數,合理使用系統資源,提高應用性能
3.可以有效的控制線程的執行,比如定時執行,取消執行等
Android中的線程池其實源于Java,都是通過對ThreadPoolExecutor進行不同配置來實現的,那么我們今天就從這這個ThreadPoolExecutor來開始吧!
ThreadPoolExecutor有四個重載的構造方法,我們這里來說說參數最多的那一個重載的構造方法,這樣大家就知道其他方法參數的含義了,如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 線程池中核心線程的數量
maximumPoolSize : 線程池中最大線程數量
keepAliveTime: 非核心線程的超時時長,當系統中非核心線程閑置時間超過keepAliveTime之后,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,則該參數也表示核心線程的超時時長
unit: 第三個參數的單位,有納秒、微秒、毫秒、秒、分、時、天等
workQueue: 線程池中的任務隊列,該隊列主要用來存儲已經被提交但是尚未執行的任務。存儲在這里的任務是由ThreadPoolExecutor的execute方法提交來的。
threadFactory: 為線程池提供創建新線程的功能,這個我們一般使用默認即可
handler: 拒絕策略,當線程無法執行新任務時(一般是由于線程池中的線程數量已經達到最大數或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。
附:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小時
TimeUnit.MINUTES; //分鐘
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //納秒
除此之外,還要說的是workQueue是一個BlockingQueue類型,而BlockingQueue是一個特殊的隊列,當我們從BlockingQueue中取數據時,如果BlockingQueue是空的,則取數據的操作會進入到阻塞狀態,當BlockingQueue中有了新數據時,這個取數據的操作又會被重新喚醒。同理,如果BlockingQueue中的數據已經滿了,往BlockingQueue中存數據的操作又會進入阻塞狀態,直到BlockingQueue中又有新的空間,存數據的操作又會被沖洗喚醒。
下面說說BlockingQueue的幾個常用的實現類:
1.ArrayBlockingQueue:這個表示一個規定了大小的BlockingQueue,ArrayBlockingQueue的構造函數接受一個int類型的數據,該數據表示BlockingQueue的大小,存儲在ArrayBlockingQueue中的元素按照FIFO(先進先出)的方式來進行存取。
2.LinkedBlockingQueue:這個表示一個大小不確定的BlockingQueue,在LinkedBlockingQueue的構造方法中可以傳一個int類型的數據,這樣創建出來的LinkedBlockingQueue是有大小的,也可以不傳,不傳的話,LinkedBlockingQueue的大小就為Integer.MAX_VALUE。
3.PriorityBlockingQueue:這個隊列和LinkedBlockingQueue類似,不同的是PriorityBlockingQueue中的元素不是按照FIFO來排序的,而是按照元素的Comparator來決定存取順序的(這個功能也反映了存入PriorityBlockingQueue中的數據必須實現了Comparator接口)。
4.SynchronousQueue:這個是同步Queue,屬于線程安全的BlockingQueue的一種,在SynchronousQueue中,生產者線程的插入操作必須要等待消費者線程的移除操作,Synchronous內部沒有數據緩存空間,因此我們無法對SynchronousQueue進行讀取或者遍歷其中的數據,元素只有在你試圖取走的時候才有可能存在。
線程池工作流程:
1.corePoolSize 也稱為工作線程,沒有任務到來之前就創建corePoolSize個線程。當一個任務到來的時候,如果任務隊列為空,工作線程也有空閑,就啟用一個工作線程處理任務。
2.如果工作線程都處于運行的狀態,而任務隊列workQueue還沒有滿,則往隊列中放入線程信息。
3.如果工作線程都處于運行狀態,任務隊列又滿了,maximumPoolSize大于corePoolSize。就會新建一個非核心進程處理新到來的任務。
4.如果非核心進程在keepAliveTime時間內都處于空閑狀態就會被回收掉。
5.或者如果線程池中的線程數已經超過非核心線程數,則拒絕執行該任務。
我們在實際開發中如果需要自己來配置這些參數,該如何配置呢?參考一下AsyncTask,AsyncTask部分源碼如下:
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
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);
....
....
}
核心線程數為手機CPU數量+1(cpu數量獲取方式Runtime.getRuntime().availableProcessors()),最大線程數為手機CPU數量×2+1,線程隊列的大小為128,OK,那么小伙伴們在以后使用線程池的過程中可以參考這個再結合自己的實際情況來配置參數。
接下來我們就來看看一下四種系統配置好的提供給我們的線程池:
1.FixedThreadPool
FixedThreadPool是一個核心線程和最大線程數量相等的的線程池,因此線程池中的線程數目是一定的,創建方式如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
且線程的超時時間為0,說明核心線程即使在沒有任務可執行的時候也不會被銷毀
2.SingleThreadExecutor
singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心線程數只有1。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
3.CachedThreadPool
CachedTreadPool一個最大的優勢是它可以根據程序的運行情況自動來調整線程池中的線程數量。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
CachedThreadPool中是沒有核心線程的,但是它的最大線程數卻為Integer.MAX_VALUE,另外,它是有線程超時機制的,超時時間為60秒,這里它使用了SynchronousQueue作為線程隊列,SynchronousQueue的特點上文已經說過了,這里不再贅述。由于最大線程數為無限大,所以每當我們添加一個新任務進來的時候,如果線程池中有空閑的線程,則由該空閑的線程執行新任務,如果沒有空閑線程,則創建新線程來執行任務。根據CachedThreadPool的特點,我們可以在有大量任務請求的時候使用CachedThreadPool,因為當CachedThreadPool中沒有新任務的時候,它里邊所有的線程都會因為超時而被終止。
4.ScheduledThreadPool
ScheduledThreadPool是一個具有定時定期執行任務功能的線程池。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
它的核心線程數量是固定的(我們在構造的時候傳入的),但是非核心線程是無窮大,當非核心線程閑置時,則會被立即回收。
它擁有幾種特殊的啟動任務的方式:
1.延遲啟動任務:
scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);
延遲1秒啟動任務。
2.延遲定時執行任務:
scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
延遲1秒之后每隔1秒執行一次新任務。
3.延遲執行任務
scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
第一次延遲1秒之后,以后每次也延遲1秒執行。
scheduleAtFixedRate周期性的,不管當前任務執行完沒有,到了時間就執行下一個任務。
scheduleWithFixedDelay,必須當前任務執行完畢,然后延遲所設置的時間就執行下一個任務。
線程池的常用功能:
1.shutDown() 關閉線程池,停止接收新任務,執行完線程池中的任務和任務隊列中的任務才關閉線程池
2.shutDownNow() 關閉線程池,停止接收新任務,并嘗試去終止正在執行的線程
3.allowCoreThreadTimeOut(boolean value) 允許核心線程閑置超時時被回收
4.submit 一般情況下我們使用execute來提交任務,但是有時候可能也會用到submit,使用submit的好處是submit有返回值,submit通常與callable線程相聯系。
Android ListView異步加載圖片亂序問題,原因分析及解決方案:可以使用AsynTask進行網絡圖片的下載,而AsynTask的原理就是線程池。所有提交的異步任務都會在這個線程池中的工作線程內執行,當工作線程需要跟UI線程交互時,工作線程會通過向在UI線程創建的Handler。傳遞消息的方式,調用相關的回調函數,從而實現UI界面的更新。
參考博客:http://m.blog.csdn.net/u012702547/article/details/52259529