無論在Java的開發中還是在Android的開發中,線程都占有重要的地位,所以今天就來說說線程池的東西。
在Android開發中,我們經常把一個耗時任務放在一個線程中進行執行,目的就是為了避免ANR異常。但是如果我們在一個頁面開了很多線程,線程在短時間內執行結束,我們這樣頻繁的創建線程就降低了系統的運行效率。所以就有了線程池。線程池的作用是什麼呢?
線程池會根據系統的環境變量,自動或手動配置一個線程池中的線程數量,使線程的創建和回收達到一個理想的狀態,減少了系統資源的消耗,提高系統的效率。這樣我們就得出了使用線程池的幾個好處:
在jdk1.5中,java給我們提供了java.util.concurrent包,這個包裡就是關於我們線程池的使用接口和類。線程池的頂級接口就是Executor接口。它有一個execute(Runnable)方法用來執行所提交的Runnable任務。它只是一個接口,所以需要有它的實現體:
配置線程池是一件比較復雜的工作,如果我們對線程池的原理不是很了解,很容易導致配置的線程池達不到效果。所以系統給我們提供了Executors類,它提供一些靜態方法幫助我們創建一些常用的線程池。
public static ExecutorService newFixedThreadPool(int nThreads):創建固定數目線程的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。 public static ExecutorService newCachedThreadPool():創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。 public static ExecutorService newSingleThreadExecutor():創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。測試案例:
我們創建一個Text的線程,用於測試:
class Text implements Runnable{
String name;
public Text(String name){
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--" + name);
}
}
現在我們開始測試線程池的效果:
public static void main(String[]args){
//我們創建一個指定大小為3的線程池
ExecutorService exServiceFixed = Executors.newFixedThreadPool(3);
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
exServiceFixed.execute(t1);
exServiceFixed.execute(t2);
exServiceFixed.execute(t3);
exServiceFixed.execute(t4);
exServiceFixed.execute(t5);
exServiceFixed.shutdown();
}
結果:
pool-1-thread-1--t1
pool-1-thread-1--t4
pool-1-thread-1--t5
pool-1-thread-2--t2
pool-1-thread-3--t3
我們可以看到盡管我們用線程池執行了5個線程任務,但是在線程池內部僅僅有我們指定的3個線程在工作,所以達到了提高效率的作用。
public static void main(String[]args){
//我們創建一個可重用的線程池
ExecutorService exServiceCached = Executors.newCachedThreadPool();
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
exServiceCached.execute(t1);
exServiceCached.execute(t2);
exServiceCached.execute(t3);
exServiceCached.execute(t4);
exServiceCached.execute(t5);
exServiceCached.shutdown();
}
運行結果:
pool-1-thread-1--t1
pool-1-thread-2--t2
pool-1-thread-3--t3
pool-1-thread-4--t4
pool-1-thread-5--t5
線程池中開了5個線程,這個線程池的特點就是可重用,不限制大小,數量以JVM在系統中能創建的大小為准。
例如:
public static void main(String[]args){
//我們創建一個可重用的線程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
scheduledExecutorService.schedule(t1, 2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.schedule(t2, 2000, TimeUnit.MILLISECONDS);
}
注意,此時使用的ScheduledExecutorService,它是ExecutorService接口的實現接口。schedule方法,用來定時任務,線程會在指定時間後進行執行。
另外,它還有定時持續的任務,
scheduledExecutorService.scheduleAtFixedRate(t1, 1000,2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(t2, 1000,2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleWithFixedDelay(t1, 1000,2000, TimeUnit.MILLISECONDS);
每隔一段時間就去執行任務。
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-2--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
public static void main(String[]args){
//我們創建一個可重用的線程池
ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
executorServiceSingle.execute(t1);
executorServiceSingle.execute(t2);
executorServiceSingle.execute(t3);
}
結果:
pool-1-thread-1--t1
pool-1-thread-1--t2
pool-1-thread-1--t3
我們可以看到只有一個線程在執行我們的任務。
這裡補充一個知識點就是ExecutorService.submit()方法。這個方法有三個重載:
Future submit(Callable task); Future submit(Runnable task, T result); Future
public interface Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
什麼都沒實現,就一個call()方法,根據注釋,這個方法用來計算某個結果。
Future的源碼結構:
public interface Future {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中聲明了5個方法,下面依次解釋每個方法的作用:
cancel方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設置為true,則返回true,若mayInterruptIfRunning設置為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。 isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。 isDone方法表示任務是否已經完成,若任務完成,則返回true; get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。
也就是說Future提供了三種功能:
1)判斷任務是否完成;
2)能夠中斷任務;
3)能夠獲取任務執行結果。
因為Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask。
也不復雜,幾個方法判斷任務是否結束,以及通過get方法獲取任務的結果。我們直接看一個例子吧!
public class MainDemo {
public static void main(String[]args){
//我們創建一個可重用的線程池
ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
Future future = executorServiceSingle.submit(new AddCallable());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class AddCallable implements Callable{
@Override
public Integer call() throws Exception {
//執行我們的業務邏輯處理
return 2+3;
}
}
我們實現一個Callable接口用來處理我們的任務,然後通過Future來獲取任務的結果。果Executor後台線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。這也就是為什麼需要捕捉InterruptedException異常的原因。這點是不是跟我們Android的下載任務差不多。開啟一個下載任務,然後通過Handler發送的UIThread中進行處理。
同樣還有一種組合Callable+FutureTask
Task task = new Task();
FutureTask futureTask = new FutureTask(task);
Thread thread = new Thread(futureTask);
thread.start();
本著復習知識點的原則,並沒有介紹線程池的配置,大家可以去搜索相關資料學習下,復習這個的知識點,就是准備這幾天寫個Android網絡請求的簡單框架使用,所以知識點也知識簡單介紹了用法,具體的深入分析沒做。