ExecutorService線程池的用法

在Java5之後,併發線程這塊發生了根本的變化,最重要的莫過於新的啓動、調度、管理線程的一大堆API了。在Java5以後,通過 Executor來啓動線程比用Thread的start()更好。在新特徵中,可以很容易控制線程的啓動、執行和關閉過程,還可以很容易使用線程池的特性。
一、創建任務
任務就是一個實現了Runnable接口的類。
創建的時候實run方法即可。
二、執行任務
通過java.util.concurrent.ExecutorService接口對象來執行任務,該接口對象通過工具類java.util.concurrent.Executors的靜態方法來創建。
Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。
ExecutorService提供了管理終止的方法,以及可爲跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。 可以關閉 ExecutorService,這將導致其停止接受新任務。關閉後,執行程序將最後終止,這時沒有任務在執行,也沒有任務在等待執行,並且無法提交新任務。
executorService.execute(new TestRunnable());
1、創建ExecutorService
通過工具類java.util.concurrent.Executors的靜態方法來創建。
Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。
比如,創建一個ExecutorService的實例,ExecutorService實際上是一個線程池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
2、將任務添加到線程去執行
當將一個任務添加到線程池中的時候,線程池會爲每個任務創建一個線程,該線程會在之後的某個時刻自動執行。
三、關閉執行服務對象
executorService.shutdown();
五、獲取任務的執行的返回值
在Java5之後,任務分兩類:一類是實現了Runnable接口的類,一類是實現了Callable接口的類。兩者都可以被 ExecutorService執行,但是Runnable任務沒有返回值,而Callable任務有返回值。並且Callable的call()方法只能通過ExecutorService的(<T> task) 方法來執行,並且返回一個 <T><T>,是表示任務等待完成的 Future。
public interface Callable<V>
返回結果並且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做 call 的方法。
Callable 接口類似於,兩者都是爲那些其實例可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,並且無法拋出經過檢查的異常。
類包含一些從其他普通形式轉換成 Callable 類的實用方法。
Callable中的call()方法類似Runnable的run()方法,就是前者有返回值,後者沒有。
當將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,並且會返回執行結果Future對象。
同樣,將Runnable的對象傳遞給ExecutorService的submit方法,則該run方法自動在一個線程上執行,並且會返回執行結果Future對象,但是在該Future對象上調用get方法,將返回null。
遺憾的是,在Java API文檔中,這塊介紹的很糊塗,估計是翻譯人員還沒搞清楚的緣故吧。或者說是註釋不到位。下面看個例子:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;


publicclass CallableDemo {
publicstaticvoid main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();

//創建10個任務並執行
for (int i = 0; i < 10; i++) {
//使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中
Future<String> future = executorService.submit(new TaskWithResult(i));
//將任務執行結果存儲到List中
resultList.add(future);
}

//遍歷任務的結果
for (Future<String> fs : resultList) {
try {
System.out.println(fs.get()); //打印各個線程(任務)執行的結果
catch (InterruptedException e) {
e.printStackTrace();
catch (ExecutionException e) {
e.printStackTrace();
finally {
//啓動一次順序關閉,執行以前提交的任務,但不接受新任務。如果已經關閉,則調用沒有其他作用。
executorService.shutdown();
}
}
}
}


class TaskWithResult implements Callable<String> {
privateint id;

public TaskWithResult(int id) {
this.id = id;
}


public String call() throws Exception {
System.out.println("call()方法被自動調用,幹活!!! " + Thread.currentThread().getName());
//一個模擬耗時的操作
for (int i = 999999; i > 0; i--) ;
return"call()方法被自動調用,任務的結果是:" + id + " " + Thread.currentThread().getName();
}
}
運行結果:
call()方法被自動調用,幹活!!! pool-1-thread-1
call()方法被自動調用,幹活!!! pool-1-thread-3
call()方法被自動調用,幹活!!! pool-1-thread-4
call()方法被自動調用,幹活!!! pool-1-thread-6
call()方法被自動調用,幹活!!! pool-1-thread-2
call()方法被自動調用,幹活!!! pool-1-thread-5
call()方法被自動調用,任務的結果是:0 pool-1-thread-1
call()方法被自動調用,任務的結果是:1 pool-1-thread-2
call()方法被自動調用,幹活!!! pool-1-thread-2
call()方法被自動調用,幹活!!! pool-1-thread-6
call()方法被自動調用,幹活!!! pool-1-thread-4
call()方法被自動調用,任務的結果是:2 pool-1-thread-3
call()方法被自動調用,幹活!!! pool-1-thread-3
call()方法被自動調用,任務的結果是:3 pool-1-thread-4
call()方法被自動調用,任務的結果是:4 pool-1-thread-5
call()方法被自動調用,任務的結果是:5 pool-1-thread-6
call()方法被自動調用,任務的結果是:6 pool-1-thread-2
call()方法被自動調用,任務的結果是:7 pool-1-thread-6
call()方法被自動調用,任務的結果是:8 pool-1-thread-4
call()方法被自動調用,任務的結果是:9 pool-1-thread-3

幾種不同的ExecutorService線程池對象
1.newCachedThreadPool()  -緩存型池子,先查看池中有沒有以前建立的線程,如果有,就reuse.如果沒有,就建一個新的線程加入池中
-緩存型池子通常用於執行一些生存期很短的異步型任務
 因此在一些面向連接的daemon型SERVER中用得不多。
-能reuse的線程,必須是timeout IDLE內的池中線程,缺省timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池。
  注意,放入CachedThreadPool的線程不必擔心其結束,超過TIMEOUT不活動,其會自動被終止。
2. newFixedThreadPool -newFixedThreadPool與cacheThreadPool差不多,也是能reuse就用,但不能隨時建新的線程
-其獨特之處:任意時間點,最多只能有固定數目的活動線程存在,此時如果有新的線程要建立,只能放在另外的隊列中等待直到當前的線程中某個線程終止直接被移出池子
-和cacheThreadPool不同,FixedThreadPool沒有IDLE機制(可能也有,但既然文檔沒提,肯定非常長,類似依賴上層的TCP或UDP IDLE機制之類的),所以FixedThreadPool多數針對一些很穩定很固定的正規併發線程,多用於服務器
-從方法的源代碼看,cache池和fixed 池調用的是同一個底層池,只不過參數不同:
fixed池線程數固定,並且是0秒IDLE(無IDLE)
cache池線程數支持0-Integer.MAX_VALUE(顯然完全沒考慮主機的資源承受能力),60秒IDLE  
3.ScheduledThreadPool -調度型線程池
-這個池子裏的線程可以按schedule依次delay執行,或週期執行
4.SingleThreadExecutor -單例線程,任意時間池中只能有一個線程
-用的是和cache池和fixed池相同的底層池,但線程數目是1-1,0秒IDLE(無IDLE)

上面四種線程池,都使用Executor的缺省線程工廠建立線程,也可單獨定義自己的線程工廠
下面是缺省線程工廠代碼:
    static class DefaultThreadFactory implements ThreadFactory {
        static final AtomicInteger poolNumber = new AtomicInteger(1);
        final ThreadGroup group;
        final AtomicInteger threadNumber = new AtomicInteger(1);
        final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null)? s.getThreadGroup() :Thread.currentThread().getThreadGroup();
          
            namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
也可自己定義ThreadFactory,加入建立池的參數中
 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {


Executor的execute()方法
execute() 方法將Runnable實例加入pool中,並進行一些pool size計算和優先級處理
execute() 方法本身在Executor接口中定義,有多個實現類都定義了不同的execute()方法
如ThreadPoolExecutor類(cache,fiexed,single三種池子都是調用它)的execute方法如下:
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
    }

發佈了24 篇原創文章 · 獲贊 75 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章