學習JAVA線程池

一、線程池作用
線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。

基於生產者-消費者模式,其提交任務的線程相當於生產者,執行任務的線程相當於消費者,並用Runnable來表示任務

二、重要接口和類
這裏寫圖片描述

1.接口Executor
這裏寫圖片描述
定義了一個接收Runnable對象的方法execute

2.接口ExecutorService
這裏寫圖片描述
是一個比Executor使用更廣泛的子類接口,其提供了生命週期(運行、關閉、已終止)管理的方法,以及可跟蹤一個或多個異步任務執行狀況返回Future的方法。Future 表示一個任務的生命週期,並且提供了相應的方法來判斷是否已經完成或取消,以及獲取任務的結果和取消任務等。
創建一個什麼樣的ExecutorService的實例(即線程池)需要根據具體應用場景而定,不過Java給我們提供了一個Executors工廠類,它可以幫助我們很方便的創建各種類型ExecutorService線程池。

2.1 ExecutorService的使用

ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});

executorService.shutdown();

2.2 ExecutorService的執行
ExecutorService有如下幾個執行方法:

- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)

2.2.1 execute(Runnable)
這個方法接收一個Runnable實例,並且異步的執行,請看下面的實例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});

executorService.shutdown();

這個方法有個問題,就是沒有辦法獲知task的執行結果。如果我們想獲得task的執行結果,我們可以傳入一個Callable的實例(下面會介紹)。

2.2.2 submit(Runnable)
submit(Runnable)和execute(Runnable)區別是前者可以返回一個Future對象,通過返回的Future對象,我們可以檢查提交的任務是否執行完畢,請看下面執行的例子:

Future future = executorService.submit(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});

future.get();  //returns null if the task has finished correctly.

如果任務執行完成,future.get()方法會返回一個null。注意,future.get()方法會產生阻塞。

2.2.3 submit(Callable)
submit(Callable)和submit(Runnable)類似,也會返回一個Future對象,但是除此之外,submit(Callable)接收的是一個Callable的實現,Callable接口中的call()方法有一個返回值,可以返回任務的執行結果,而Runnable接口中的run()方法是void的,沒有返回值。請看下面實例:

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

System.out.println("future.get() = " + future.get());

如果任務執行完成,future.get()方法會返回Callable任務的執行結果。注意,future.get()方法會產生阻塞,直到call方法結束返回結果。

2.2.4 invokeAny(…)
invokeAny(…)方法接收的是一個Callable的集合,執行這個方法不會返回Future,但是會返回所有Callable任務中其中一個任務的執行結果。這個方法也無法保證返回的是哪個任務的執行結果,反正是其中的某一個。請看下面實例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 1";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 2";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 3";
    }
});

String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

大家可以嘗試執行上面代碼,每次執行都會返回一個結果,並且返回的結果是變化的,可能會返回“Task2”也可是“Task1”或者其它。

2.2.5 invokeAll(…)
invokeAll(…)與 invokeAny(…)類似也是接收一個Callable集合,但是前者執行之後會返回一個Future的List,其中對應着每個Callable任務執行後的Future對象。情況下面這個實例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 1";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 2";
    }
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
        return "Task 3";
    }
});

List<Future<String>> futures = executorService.invokeAll(callables);

for(Future<String> future : futures){
    System.out.println("future.get = " + future.get());
}

executorService.shutdown();

2.3 ExecutorService的關閉
當我們使用完成ExecutorService之後應該關閉它,否則它裏面的線程會一直處於運行狀態。

如果要關閉ExecutorService中執行的線程,我們可以調用ExecutorService.shutdown()方法。在調用shutdown()方法之後,ExecutorService不會立即關閉,但是它不再接收新的任務,直到當前所有線程執行完成纔會關閉,所有在shutdown()執行之前提交的任務都會被執行。

如果我們想立即關閉ExecutorService,我們可以調用ExecutorService.shutdownNow()方法。這個動作將跳過所有正在執行的任務和被提交還沒有執行的任務。但是它並不對正在執行的任務做任何保證,有可能它們都會停止,也有可能執行完成。

3.接口ScheduledExecutorService
這裏寫圖片描述

3.1 scheduleAtFixedRate() 按指定頻率週期執行某個任務

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, 
            long initialDelay,  
            long period,  
            TimeUnit unit); 

command:執行線程
initialDelay:初始化延時
period:兩次開始執行最小間隔時間
unit:計時單位
實例:

private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();  
static {
       service.scheduleAtFixedRate(new Runnable() {  
           public void run() {  
               System.out.println("延遲10s開始執行,每隔100s重新執行一次任務");
           }  
       }, 10, 100, TimeUnit.SECONDS); 
}

3.2 scheduleWithFixedDelay() 按指定頻率間隔執行某個任務

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,  
                long initialDelay,  
                long delay,  
                TimeUnit unit); 

command:執行線程
initialDelay:初始化延時
period:前一次執行結束到下一次執行開始的間隔時間(間隔執行延遲時間)
unit:計時單位
實例:

    private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();  
    static {
        service.scheduleWithFixedDelay(new Runnable() {  
            public void run() {  
                System.out.println("10秒後開始執行,本次執行結束後延遲100s開始下次執行。");
            }  
        }, 10, 100, TimeUnit.SECONDS); 
    }

3.3 週期定時執行某個任務

/** 
 * 每天晚上8點執行一次 
 * 每天定時安排任務進行執行 
 */  
public static void executeEightAtNightPerDay() {  
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);  
    long oneDay = 24 * 60 * 60 * 1000;  
    long initDelay  = getTimeMillis("20:00:00") - System.currentTimeMillis();  
    initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;  

    executor.scheduleAtFixedRate(new Runnable() {  
        public void run() {  
            System.out.println("do something");
        },  
        initDelay,  
        oneDay,  
        TimeUnit.MILLISECONDS);  
}  
/** 
 * 獲取指定時間對應的毫秒數 
 * @param time "HH:mm:ss" 
 * @return 
 */  
private static long getTimeMillis(String time) {  
    try {  
        DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");  
        DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");  
        Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);  
        return curDate.getTime();  
    } catch (ParseException e) {  
        e.printStackTrace();  
    }  
    return 0;  
}  

4.類Executors
通過 Executors 中的靜態工廠方法來創建線程池。

4.1 newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)

創建一個定長線程池,定長線程池的大小最好根據系統資源進行設置,可控制線程最大併發數,超出的線程會在隊列中等待。
每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
newFixedThreadPool內部有個任務隊列,假設線程池裏有3個線程,提交了5個任務,那麼後兩個任務就放在任務隊列了,即使前3個任務sleep或者堵塞了,也不會執行後兩個任務,除非前三個任務有執行完的

實例:

public class Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 6; i++) {
            final int index = i;
            System.out.println("task: " + (i + 1));
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    System.out.println("thread start" + index);
                    try {
                        Thread.sleep(Long.MAX_VALUE);//執行很久。。。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread end" + index);
                }
            };
            service.execute(run);
        }
        service.shutdown();
    }
}

輸出:

task: 1
task: 2
thread start0
thread start1
task: 3
task: 4
task: 5
task: 6

6個任務,只有二個任務在執行,4個在任務隊列,等候執行。

4.2 newCachedThreadPool()
創建一個可緩存線程池,如果線程池中的線程在60秒未被使用就將被移除,在執行新的任務時,當線程池中有之前創建的可用線程就重用可用線程,否則就新建一條線程

public class Test {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            });
        }
    }
}

線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
對於執行很多短期異步任務的程序而言,可提高程序性能。

4.3 newScheduledThreadPool()
創建一個定長線程池,支持定時及週期性任務執行。實例同【3.接口ScheduledExecutorService】

4.4 newSingleThreadExecutor()
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

public class Test {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

結果依次輸出,相當於順序執行各個任務。

5.類ThreadPoolExecutor

private static ExecutorService exec = new ThreadPoolExecutor(8, 8, 0L,
        TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue<Runnable>(100000),
        new ThreadPoolExecutor.CallerRunsPolicy());

5.1 常用構造方法

ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        RejectedExecutionHandler handler)
corePoolSize線程(核心)池維護線程的最少數量
maximumPoolSize線程(最大)池維護線程的最大數量
keepAliveTime 線程池維護線程所允許的空閒時間
unit線程池維護線程所允許的空閒時間的單位
workQueue線程池所使用的緩衝隊列
handler 線程池對拒絕任務的處理策略



5.1.1
一個任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是Runnable類型對象的run()方法。當一個任務通過execute(Runnable)方法欲添加到線程池時:

l 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。

l 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。

l 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。

l 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

l 當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。

5.1.2 handler有四個選擇

默認new ThreadPoolExecutor.AbortPolicy()處理程序遭到拒絕將拋出運行時RejectedExecutionException
new ThreadPoolExecutor.CallerRunsPolicy()當拋出RejectedExecutionException異常時,會調用rejectedExecution方法
new ThreadPoolExecutor.DiscardOldestPolicy()拋棄舊的任務,如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重複此過程)
new ThreadPoolExecutor.DiscardPolicy()拋棄當前的任務,不能執行的任務將被刪除



實例:(實例原文http://blog.csdn.net/pfnie/article/details/52755769
寫一個task:

public class Task implements Runnable {  

    protected String name;  

    public Task(String name) {  
        super();  
        this.name = name;  
    }  

    @Override  
    public void run() {  
        try {  
            System.out.println(this.name + " is running.");    
            Thread.sleep(500);   
        } catch (Exception e) {  

        }  
    }  
}  

1) AbortPolicy 示例

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.RejectedExecutionException;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  

public class AbortPolicyDemo {  

    public static void main(String[] args) {  
        // 創建線程池。線程池的"最大池大小"和"核心池大小"都爲1,"線程池"的阻塞隊列容量爲1。    
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0,  
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));  

        // 設置線程池的拒絕策略爲AbortPolicy    
        pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 設置線程池的拒絕策略爲CallerRunsPolicy    
        // pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  
        // 設置線程池的拒絕策略爲DiscardPolicy    
        // pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 
        // 設置線程池的拒絕策略爲DiscardOldestPolicy    
        // pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());          
        try {  
            // 新建10個任務,並將它們添加到線程池中    
            for (int i = 0; i < 10; i++) {    
                Runnable myTask = new Task("task-"+i);    
                pool.execute(myTask);    
            }    
        } catch (RejectedExecutionException e) {  
            e.printStackTrace();  
             // 關閉線程池    
            pool.shutdown();  
        }  
    }  
} 

某一次運行結果,直接已異常的形式拋出:

task-0 is running.

java.util.concurrent.RejectedExecutionException: Task websocket.blocktest.Task@58777255 rejected from java.util.concurrent.ThreadPoolExecutor@470ae2bf[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
    at websocket.blocktest.Test.main(Test.java:22)

task-1 is running.



2)CallerRunsPolicy 示例

// 設置線程池的拒絕策略爲CallerRunsPolicy    
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 

某一次運行結果,當有新任務添加到線程池被拒絕時,線程池會將被拒絕的任務添加到”線程池正在運行的線程”中去運行:

task-2 is running.
task-0 is running.
task-3 is running.
task-1 is running.
task-5 is running.
task-4 is running.
task-7 is running.
task-6 is running.
task-9 is running.
task-8 is running.



3)DiscardPolicy 示例

// 設置線程池的拒絕策略爲DiscardPolicy    
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 

某一次運行結果,線程池pool的”最大池大小”和”核心池大小”都爲1,這意味着”線程池能同時運行的任務數量最大隻能是1”。線程池pool的阻塞隊列是ArrayBlockingQueue,ArrayBlockingQueue是一個有界的阻塞隊列,ArrayBlockingQueue的容量爲1。這也意味着線程池的阻塞隊列只能有一個線程池阻塞等待。由此可知,線程池中共運行了2個任務。第1個任務直接放到Worker中,通過線程去執行;第2個任務放到阻塞隊列中等待。其他的任務都被丟棄了。

task-0 is running.
task-1 is running.



4)DiscardOldestPolicy 示例

// 設置線程池的拒絕策略爲DiscardOldestPolicy    
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());

某一次運行結果,當有新任務添加到線程池被拒絕時,線程池會丟棄阻塞隊列中末尾的任務,然後將被拒絕的任務添加到末尾。

task-0 is running.
task-9 is running.


5.2 ThreadPoolExecutor配置(加深理解)

5.2.1 線程的創建與銷燬
1)核心池大小、最大池大小和存活時間共同管理着線程的創建與銷燬。
2)核心池的大小是目標的大小;線程池的實現試圖維護池的大小;即使沒有任務執行,池的大小也等於核心池的大小,並直到工作隊列充滿前,池都不會創建更多的線程。如果當前池的大小超過了核心池的大小,線程池就會終止它。
3)最大池的大小是可同時活動的線程數的上限。
4)如果一個線程已經閒置的時間超過了存活時間,它將成爲一個被回收的候選者。
5)newFixedThreadPool工廠爲請求的池設置了核心池的大小和最大池的大小,而且池永遠不會超時
6)newCacheThreadPool工廠將最大池的大小設置爲Integer.MAX_VALUE,核心池的大小設置爲0,超時設置爲一分鐘。這樣創建了無限擴大的線程池,會在需求量減少的情況下減少線程數量。

5.2.2 管理
1)ThreadPoolExecutor允許你提供一個BlockingQueue來持有等待執行的任務。任務排隊有3種基本方法:無限隊列、有限隊列和同步移交。
2)newFixedThreadPool和newSingleThreadExectuor默認使用的是一個無限的 LinkedBlockingQueue。如果所有的工作者線程都處於忙碌狀態,任務會在隊列中等候。如果任務持續快速到達,超過了它們被執行的速度,隊列也會無限制地增加。穩妥的策略是使用有限隊列,比如ArrayBlockingQueue或有限的LinkedBlockingQueue以及 PriorityBlockingQueue。
3)對於龐大或無限的池,可以使用SynchronousQueue,完全繞開隊列,直接將任務由生產者交給工作者線程
4)可以使用PriorityBlockingQueue通過優先級安排任務

6.類ScheduledThreadPoolExecutor
平時我們在執行一個定時任務時,會採用Time和TimeTask來組合處理,但是Timer和TimerTask存在一些缺陷:
1)Timer只創建了一個線程。當你的任務執行的時間超過設置的延時時間將會產生一些問題。
2)Timer創建的線程沒有處理異常,因此一旦拋出非受檢異常,該線程會立即終止。
JDK 5.0以後推薦使用ScheduledThreadPoolExecutor。該類屬於Executor Framework,它除了能處理異常外,還可以創建多個線程解決上面的問題。

實例:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(5);
        for (int i = 0; i < 5; i ++){
            final int temp = i + 1;
            //jdk8 lambda表達式
            pool.schedule(() -> {
                System.out.println("第"+temp+"個炸彈爆炸時間:" + simpleDateFormat.format(new Date()));
            }, temp * 5, TimeUnit.SECONDS);

          //隔2秒後開始執行任務,並且在上一次任務開始後隔一秒再執行一次;  
          //pool.scheduleWithFixedDelay(new MyTask(), 2, 1, TimeUnit.SECONDS);  
          //隔6秒後執行一次,但只會執行一次。  
          //pool.schedule(new MyTask(), 6, TimeUnit.SECONDS);  
        }
        pool.shutdown();
        System.out.println("end main時間:" + simpleDateFormat.format(new Date()));
    }

    private static class MyTask implements Runnable{
        @Override
        public void run() { 
            //捕獲所有的異常,保證定時任務能夠繼續執行
            try{
                System.out.println("任務開始...");
                // 業務...
                System.out.println("任務結束...");
            }catch (Throwable e) {
                // 什麼也不做
            }
        }
    }
}

在JAVAWEB開發中,執行定時任務有一個更好的選擇: Quartz

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章