Java程序員必備知識-多線程框架Executor詳解

爲何引入Executor線程池框架

new Thread()的缺點

每次new Thread()耗費性能
調用new Thread()建立的線程缺少管理,被稱爲野線程,並且能夠無限制建立,之間相互競爭,會致使過多佔用系統資源致使系統癱瘓。
不利於擴展,好比如定時執行、按期執行、線程中斷javascript

採用線程池的優勢

重用存在的線程,減小對象建立、消亡的開銷,性能佳
可有效控制最大併發線程數,提升系統資源的使用率,同時避免過多資源競爭,避免堵塞
提供定時執行、按期執行、單線程、併發數控制等功能html

Executor的介紹

在Java 5以後,併發編程引入了一堆新的啓動、調度和管理線程的API。java

Executor框架即是Java 5中引入的,

其內部使用了線程池機制,它在java.util.cocurrent 包下,經過該框架來控制線程的啓動、執行和關閉,能夠簡化併發編程的操做。所以,在Java 5以後,經過Executor來啓動線程比使用Thread的start方法更好,除了更易管理,效率更好(用線程池實現,節約開銷)外,還有關鍵的一點:有助於避免this逃逸問題——若是咱們在構造器中啓動一個線程,由於另外一個任務可能會在構造器結束以前開始執行,此時可能會訪問到初始化了一半的對象用Executor在構造器中。面試

Executor框架包括:線程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。編程

Executors方法介紹

Executors工廠類

經過Executors提供四種線程池,newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool。緩存

1.public static ExecutorService newFixedThreadPool(int nThreads)
建立固定數目線程的線程池。服務器

2.public static ExecutorService newCachedThreadPool()
建立一個可緩存的線程池,調用execute將重用之前構造的線程(若是線程可用)。若是現有線程沒有可用的,則建立一個新線 程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。微信

3.public static ExecutorService newSingleThreadExecutor()
建立一個單線程化的Executor。markdown

4.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
建立一個支持定時及週期性的任務執行的線程池,多數狀況下可用來替代Timer類。多線程

1.newFixedThreadPool建立一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。

示例
 ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

運行結果:總共只會建立5個線程, 開始執行五個線程,當五個線程都處於活動狀態,再次提交的任務都會加入隊列等到其餘線程運行結束,當線程處於空閒狀態時會被下一個任務複用

2.newCachedThreadPool建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程

示例:

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

運行結果:能夠看出緩存線程池大小是不定值,能夠須要建立不一樣數量的線程,在使用緩存型池時,先查看池中有沒有之前建立的線程,若是有,就複用.若是沒有,就新建新的線程加入池中,緩存型池子一般用於執行一些生存期很短的異步型任務

3.newScheduledThreadPool建立一個定長線程池,支持定時及週期性任務執行

schedule(Runnable command,long delay, TimeUnit unit)建立並執行在給定延遲後啓用的一次性操做

示例:表示從提交任務開始計時,5000毫秒後執行
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.schedule(syncRunnable, 5000, TimeUnit.MILLISECONDS);
        }

運行結果和newFixedThreadPool相似,不一樣的是newScheduledThreadPool是延時必定時間以後才執行

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)

建立並執行一個在給定初始延遲後首次啓用的按期操做,後續操做具備給定的週期;也就是將在 initialDelay 後開始執行,而後在initialDelay+period 後執行,接着在 initialDelay + 2 * period 後執行,依此類推

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        Runnable syncRunnable = new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, Thread.currentThread().getName());
            }
        };
        executorService.scheduleAtFixedRate(syncRunnable, 5000, 3000, TimeUnit.MILLISECONDS);

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

建立並執行一個在給定初始延遲後首次啓用的按期操做,隨後,在每一次執行終止和下一次執行開始之間都存在給定的延遲

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        Runnable syncRunnable = new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorService.scheduleWithFixedDelay(syncRunnable, 5000, 3000, TimeUnit.MILLISECONDS);

4.newSingleThreadExecutor建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行

ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

運行結果:只會建立一個線程,當上一個執行完以後纔會執行第二個

ExecutorService

ExecutorService是一個接口,ExecutorService接口繼承了Executor接口,定義了一些生命週期的方法。

public interface ExecutorService extends Executor {


    void shutdown();//順次地關閉ExecutorService,中止接收新的任務,等待全部已經提交的任務執行完畢以後,關閉ExecutorService


    List<Runnable> shutdownNow();//阻止等待任務啓動並試圖中止當前正在執行的任務,中止接收新的任務,返回處於等待的任務列表


    boolean isShutdown();//判斷線程池是否已經關閉

    boolean isTerminated();//若是關閉後全部任務都已完成,則返回 true。注意,除非首先調用 shutdown 或 shutdownNow,不然 isTerminated 永不爲 true。


    boolean awaitTermination(long timeout, TimeUnit unit)//等待(阻塞)直到關閉或最長等待時間或發生中斷,timeout - 最長等待時間 ,unit - timeout 參數的時間單位 若是此執行程序終止,則返回 true;若是終止前超時期滿,則返回 false 


    <T> Future<T> submit(Callable<T> task);//提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。該 Future 的 get 方法在成功完成時將會返回該任務的結果。


    <T> Future<T> submit(Runnable task, T result);//提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功完成時將會返回給定的結果。


    Future<?> submit(Runnable task);//提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功 完成時將會返回 null


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)//執行給定的任務,當全部任務完成時,返回保持任務狀態和結果的 Future 列表。返回列表的全部元素的 Future.isDone() 爲 true。
        throws InterruptedException;


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)//執行給定的任務,當全部任務完成時,返回保持任務狀態和結果的 Future 列表。返回列表的全部元素的 Future.isDone() 爲 true。
        throws InterruptedException;


    <T> T invokeAny(Collection<? extends Callable<T>> tasks)//執行給定的任務,若是在給定的超時期滿前某個任務已成功完成(也就是未拋出異常),則返回其結果。一旦正常或異常返回後,則取消還沒有完成的任務。
        throws InterruptedException, ExecutionException;


    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService接口繼承自Executor接口,它提供了更豐富的實現多線程的方法,好比,ExecutorService提供了關閉本身的方法,以及可爲跟蹤一個或多個異步任務執行情況而生成 Future 的方法。 能夠調用ExecutorService的shutdown()方法來平滑地關閉 ExecutorService,調用該方法後,將致使ExecutorService中止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另外一類是尚未開始執行的),當全部已經提交的任務執行完畢後將會關閉ExecutorService。所以咱們通常用該接口來實現和管理多線程。

ExecutorService的生命週期包括三種狀態:運行、關閉、終止。建立後便進入運行狀態,當調用了shutdown()方法時,便進入關閉狀態,此時意味着ExecutorService再也不接受新的任務,但它還在執行已經提交了的任務,當素有已經提交了的任務執行完後,便到達終止狀態。若是不調用shutdown()方法,ExecutorService會一直處在運行狀態,不斷接收新的任務,執行新的任務,服務器端通常不須要關閉它,保持一直運行便可。

Executor執行Runnable任務

一旦Runnable任務傳遞到execute()方法,該方法便會自動在一個線程上執行。下面是是Executor執行Runnable任務的示例代碼:

public class TestCachedThreadPool{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
// ExecutorService executorService = Executors.newFixedThreadPool(5); 
// ExecutorService executorService = Executors.newSingleThreadExecutor(); 
        for (int i = 0; i < 5; i++){   
            executorService.execute(new TestRunnable());   
            System.out.println("************* a" + i + " *************");   
        }   
        executorService.shutdown();   
    }   
}   

class TestRunnable implements Runnable{   
    public void run(){   
        System.out.println(Thread.currentThread().getName() + "線程被調用了。");   
    }   
}

結果

這裏寫圖片描述

Executor執行Callable任務

在Java 5以後,任務分兩類:一類是實現了Runnable接口的類,一類是實現了Callable接口的類。二者均可以被ExecutorService執行,可是Runnable任務沒有返回值,而Callable任務有返回值。而且Callable的call()方法只能經過ExecutorService的submit(Callable task) 方法來執行,而且返回一個 Future,是表示任務等待完成的 Future。

下面給出一個Executor執行Callable任務的示例代碼:

public class CallableDemo{   
    public static void 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{   
                    while(!fs.isDone);//Future返回若是沒有完成,則一直循環等待,直到Future返回完成 
                    System.out.println(fs.get());     //打印各個線程(任務)執行的結果 
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{   
                    //啓動一次順序關閉,執行之前提交的任務,但不接受新任務 
                    executorService.shutdown();   
                }   
        }   
    }   
}   


class TaskWithResult implements Callable<String>{   
    private int id;   

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

    /** * 任務的具體過程,一旦任務傳給ExecutorService的submit方法, * 則該方法自動在一個線程上執行 */   
    public String call() throws Exception {  
        System.out.println("call()方法被自動調用!!! " + Thread.currentThread().getName());   
        //該返回結果將被Future的get方法獲得 
        return "call()方法被自動調用,任務返回的結果是:" + id + " " + Thread.currentThread().getName();   
    }   
}

某次執行結果以下:

這裏寫圖片描述

從結果中能夠一樣能夠看出,submit也是首先選擇空閒線程來執行任務,若是沒有,纔會建立新的線程來執行任務。另外,須要注意:若是Future的返回還沒有完成,則get()方法會阻塞等待,直到Future完成返回,能夠經過調用isDone()方法判斷Future是否完成了返回。

自定義線程池

自定義線程池,能夠用ThreadPoolExecutor類建立,它有多個構造方法來建立線程池,用該類很容易實現自定義的線程池,這裏先貼上示例程序:

public class ThreadPoolTest{   
    public static void main(String[] args){   
        //建立等待隊列 
        BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);   
        //建立線程池,池中保存的線程數爲3,容許的最大線程數爲5 
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);   
        //建立七個任務 
        Runnable t1 = new MyThread();   
        Runnable t2 = new MyThread();   
        Runnable t3 = new MyThread();   
        Runnable t4 = new MyThread();   
        Runnable t5 = new MyThread();   
        Runnable t6 = new MyThread();   
        Runnable t7 = new MyThread();   
        //每一個任務會在一個線程上執行 
        pool.execute(t1);   
        pool.execute(t2);   
        pool.execute(t3);   
        pool.execute(t4);   
        pool.execute(t5);   
        pool.execute(t6);   
        pool.execute(t7);   
        //關閉線程池 
        pool.shutdown();   
    }   
}   

class MyThread implements Runnable{   
    @Override   
    public void run(){   
        System.out.println(Thread.currentThread().getName() + "正在執行。。。");   
        try{   
            Thread.sleep(100);   
        }catch(InterruptedException e){   
            e.printStackTrace();   
        }   
    }   
}

參考

http://gold.xitu.io/entry/57cbaf667db2a2007895256e
http://blog.csdn.net/ns_code/article/details/17465497
http://www.infoq.com/cn/articles/executor-framework-thread-pool-task-execution-part-01
http://www.cnblogs.com/limingluzhu/p/4858776.html

個人微信二維碼以下,歡迎交流討論

這裏寫圖片描述

歡迎關注《IT面試題彙總》微信訂閱號。天天推送經典面試題和麪試心得技巧

微信訂閱號二維碼以下:

這裏寫圖片描述

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
相關文章
相關標籤/搜索