Java多線程之Executor框架<Callable辟狈、Future、Executor和ExecutorService>

引言

Executor框架是指JDK 1.5中引入的一系列并發(fā)庫(kù)中與Executor相關(guān)的功能類,包括Executor哼转、Executors明未、ExecutorService、Future壹蔓、Callable等趟妥。

一、為什么要引入Executor框架佣蓉?

1披摄、如果使用new Thread(...).start()的方法處理多線程,有如下缺點(diǎn):

① 開銷大勇凭。對(duì)于JVM來說疚膊,每次新建線程和銷毀線程都會(huì)有很大的開銷。

② 線程缺乏管理虾标。沒有一個(gè)池來限制線程的數(shù)量寓盗,如果并發(fā)量很高,會(huì)創(chuàng)建很多線程璧函,而且線程之間可能會(huì)有相互競(jìng)爭(zhēng)傀蚌,這將會(huì)過多占用系統(tǒng)資源,增加系統(tǒng)資源的消耗量蘸吓。而且線程數(shù)量超過系統(tǒng)負(fù)荷喳张,容易導(dǎo)致系統(tǒng)不穩(wěn)定。

2美澳、使用線程池的方法销部,有如下優(yōu)點(diǎn):

① 線程復(fù)用。通過復(fù)用創(chuàng)建了的線程制跟,減少了線程的創(chuàng)建舅桩、消亡的開銷。

② 有效控制并發(fā)線程數(shù)雨膨。

③ 提供了更簡(jiǎn)單靈活的線程管理擂涛。可以提供定時(shí)執(zhí)行聊记、單線程撒妈、可變線程數(shù)等多種使用功能。

二排监、Executor框架的UML圖

image

三狰右、下面開始分析一下Executor框架中幾個(gè)比較重要的接口和類。

1舆床、Callable

Callable位于java.util.concurrent包下棋蚌,它是一個(gè)接口嫁佳,只聲明了一個(gè)call()方法。

Callable接口類似于Runnable谷暮,兩者都是為了可能在線程中執(zhí)行的類而設(shè)計(jì)的蒿往。和Runnable接口中的run()方法類似,Callable 提供的call()方法作為線程的執(zhí)行體湿弦。但是call()比run()方法更強(qiáng)大瓤漏,體現(xiàn)在

① call方法有返回值。

② call方法可以聲明拋出異常颊埃。

2蔬充、Future

Future 接口位于java.util.concurrent包下,是Java 1.5中引入的接口竟秫。

Future主要用來對(duì)具體的Runnable或Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成跷乐、獲取結(jié)果肥败。必要時(shí)可以通過get()方法獲取執(zhí)行結(jié)果,get()方法會(huì)阻塞知道任務(wù)返回結(jié)果愕提。

當(dāng)你提交一個(gè)Callable對(duì)象給線程池時(shí)馒稍,將得到一個(gè)Future對(duì)象,并且它和你傳入的Callable示例有相同泛型浅侨。

Future 接口中的5個(gè)方法:

public interface Future<V> {
    //用來取消任務(wù)
    //參數(shù)mayInterruptIfRunning表示是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù)纽谒。
    boolean cancel(boolean mayInterruptIfRunning);
    //表示任務(wù)是否被取消成功
    boolean isCancelled();
    //表示任務(wù)是否已經(jīng)完成
    boolean isDone();
    //用來獲取執(zhí)行結(jié)果,這個(gè)方法會(huì)產(chǎn)生阻塞如输,會(huì)一直等到任務(wù)執(zhí)行完畢才返回鼓黔;
    V get()
    //用來獲取執(zhí)行結(jié)果,如果在指定時(shí)間內(nèi)不见,還沒獲取到結(jié)果澳化,會(huì)拋出TimeoutException異常。
    V get(long timeout, TimeUnit unit)
}

Future提供了三種功能:

① 判斷任務(wù)是否完成

② 能夠中斷任務(wù)

③ 能夠獲取任務(wù)執(zhí)行結(jié)果

3稳吮、Executor

Executor是一個(gè)接口缎谷,它將任務(wù)的提交與任務(wù)的執(zhí)行分離開來,定義了一個(gè)接收Runnable對(duì)象的方法executor灶似。Executor是Executor框架中最基礎(chǔ)的一個(gè)接口列林,類似于集合中的Collection接口。

4酪惭、ExecutorService

ExecutorService繼承了Executor希痴,是一個(gè)比Executor使用更廣泛的子類接口。定義了終止任務(wù)春感、提交任務(wù)润梯、跟蹤任務(wù)返回結(jié)果等方法。

一個(gè)ExecutorService是可以關(guān)閉的,關(guān)閉之后它將不能再接收任何任務(wù)纺铭,對(duì)于不在使用的ExecutorService寇钉,應(yīng)該將其關(guān)閉以釋放資源。

ExecutorService方法介紹:

package java.util.concurrent;

import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {

    /**
     * 平滑地關(guān)閉線程池舶赔,已經(jīng)提交到線程池中的任務(wù)會(huì)繼續(xù)執(zhí)行完扫倡。
     */
    void shutdown();

    /**
     * 立即關(guān)閉線程池,返回還沒有開始執(zhí)行的任務(wù)列表竟纳。
     * 會(huì)嘗試中斷正在執(zhí)行的任務(wù)(每個(gè)線程調(diào)用 interruput方法)撵溃,但這個(gè)行為不一定會(huì)成功。
     */
    List<Runnable> shutdownNow();

    /**
     * 判斷線程池是否已經(jīng)關(guān)閉
     */
    boolean isShutdown();

    /**
     * 判斷線程池的任務(wù)是否已經(jīng)執(zhí)行完畢锥累。
     * 注意此方法調(diào)用之前需要先調(diào)用shutdown()方法或者shutdownNow()方法缘挑,否則總是會(huì)返回false
     */
    boolean isTerminated();

    /**
     * 判斷線程池的任務(wù)是否都執(zhí)行完。
     * 如果沒有任務(wù)沒有執(zhí)行完畢則阻塞桶略,直至任務(wù)完成或者達(dá)到了指定的timeout時(shí)間就會(huì)返回
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 提交帶有一個(gè)返回值的任務(wù)到線程池中去執(zhí)行(回調(diào))语淘,返回的 Future 表示任務(wù)的待定結(jié)果。
     * 當(dāng)任務(wù)成功完成后际歼,通過 Future 實(shí)例的 get() 方法可以獲取該任務(wù)的結(jié)果惶翻。
     * Future 的 get() 方法是會(huì)阻塞的。
     */
    <T> Future<T> submit(Callable<T> task);

    /**
     *提交一個(gè)Runnable的任務(wù)鹅心,當(dāng)任務(wù)完成后吕粗,可以通過Future.get()獲取的是提交時(shí)傳遞的參數(shù)T result
     * 
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交一個(gè)Runnable的人無語,它的Future.get()得不到任何內(nèi)容旭愧,它返回值總是Null颅筋。
     * 為什么有這個(gè)方法?為什么不直接設(shè)計(jì)成void submit(Runnable task)這種方式输枯?
     * 這是因?yàn)镕uture除了get這種獲取任務(wù)信息外垃沦,還可以控制任務(wù),
     具體體現(xiàn)在 Future的這個(gè)方法上:boolean cancel(boolean mayInterruptIfRunning)
     這個(gè)方法能夠去取消提交的Rannable任務(wù)。
     */
    Future<?> submit(Runnable task);

    /**
     * 執(zhí)行一組給定的Callable任務(wù),返回對(duì)應(yīng)的Future列表捐晶。列表中每一個(gè)Future都將持有該任務(wù)的結(jié)果和狀態(tài)寺谤。
     * 當(dāng)所有任務(wù)執(zhí)行完畢后,方法返回,此時(shí)并且每一個(gè)Future的isDone()方法都是true。
     * 完成的任務(wù)可能是正常結(jié)束,也可以是異常結(jié)束
     * 如果當(dāng)任務(wù)執(zhí)行過程中收夸,tasks集合被修改了,那么方法的返回結(jié)果將是不確定的血崭,
       即不能確定執(zhí)行的是修改前的任務(wù)卧惜,還是修改后的任務(wù)
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 執(zhí)行一組給定的Callable任務(wù)厘灼,返回對(duì)應(yīng)的Future列表。列表中每一個(gè)Future都將持有該任務(wù)的結(jié)果和狀態(tài)咽瓷。
     * 當(dāng)所有任務(wù)執(zhí)行完畢后或者超時(shí)后设凹,方法將返回,此時(shí)并且每一個(gè)Future的isDone()方法都是true茅姜。
     * 一旦方法返回闪朱,未執(zhí)行完成的任務(wù)被取消,而完成的任務(wù)可能正常結(jié)束或者異常結(jié)束钻洒, 
     * 完成的任務(wù)可以是正常結(jié)束奋姿,也可以是異常結(jié)束
     * 如果當(dāng)任務(wù)執(zhí)行過程中,tasks集合被修改了素标,那么方法的返回結(jié)果將是不確定的
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 執(zhí)行一組給定的Callable任務(wù)称诗,當(dāng)成功執(zhí)行完(沒拋異常)一個(gè)任務(wù)后此方法便返回,返回的是該任務(wù)的結(jié)果
     * 一旦此正常返回或者異常結(jié)束头遭,未執(zhí)行的任務(wù)都會(huì)被取消寓免。 
     * 如果當(dāng)任務(wù)執(zhí)行過程中,tasks集合被修改了任岸,那么方法的返回結(jié)果將是不確定的
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 執(zhí)行一組給定的Callable任務(wù)再榄,當(dāng)在timeout(超時(shí))之前成功執(zhí)行完(沒拋異常)一個(gè)任務(wù)后此方法便返回狡刘,返回的是該任務(wù)的結(jié)果
     * 一旦此正常返回或者異常結(jié)束享潜,未執(zhí)行的任務(wù)都會(huì)被取消。 
     * 如果當(dāng)任務(wù)執(zhí)行過程中嗅蔬,tasks集合被修改了剑按,那么方法的返回結(jié)果將是不確定的
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

shutdown() 和 shutdownNow() 是用來關(guān)閉連接池的兩個(gè)方法,而且這兩個(gè)方法都是在當(dāng)前線程立即返回澜术,不會(huì)阻塞至線程池中的方法執(zhí)行結(jié)束艺蝴。調(diào)用這兩個(gè)方法之后,連接池將不能再接受任務(wù)鸟废。

下面給寫幾個(gè)示例來加深ExecutorService的方法的理解猜敢。
先寫兩個(gè)任務(wù)類:ShortTask和LongTask,這兩個(gè)類都繼承了Runnable接口盒延,ShortTask的run()方法執(zhí)行很快缩擂,LongTask的run()方法執(zhí)行時(shí)間為10s。

public class LongTask implements Runnable {
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("complete a long task");
    }

}
public class ShortTask implements Runnable {
    @Override
    public void run() {
        System.out.println("complete a short task...");
    }

}

測(cè)試shutdown()方法

package OSChina.Client;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

public class Client10 {
    public static void main(String[] args) {
        ExecutorService threadpool = Executors.newFixedThreadPool(4);
        threadpool.submit(new ShortTask());
        threadpool.submit(new ShortTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new ShortTask());
        threadpool.shutdown();
        boolean isShutdown = threadpool.isShutdown();
        System.out.println("線程池是否已經(jīng)關(guān)閉:" + isShutdown);
        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        try {
            while (!threadpool.awaitTermination(1L, TimeUnit.SECONDS)){
                System.out.println("線程池中還有任務(wù)在執(zhí)行添寺,當(dāng)前時(shí)間:" + sdf.format(new Date()));
            }
            System.out.println("線程池中已經(jīng)沒有在執(zhí)行的任務(wù)胯盯,線程池已完全關(guān)閉!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image

測(cè)試shutdownNow()方法

package OSChina.Client;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Client11 {
    public static void main(String[] args) {
        ExecutorService threadpool = Executors.newFixedThreadPool(3);
        //將5個(gè)任務(wù)提交到有3個(gè)線程的線程池
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        try {
            TimeUnit.SECONDS.sleep(1L);
            //關(guān)閉線程池
            List<Runnable> waiteRunnables = threadpool.shutdownNow();
            System.out.println("還沒有執(zhí)行的任務(wù)數(shù):" + waiteRunnables.size());

            boolean isShutdown = threadpool.isShutdown();
            System.out.println("線程池是否已經(jīng)關(guān)閉:" + isShutdown);

            final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            while (!threadpool.awaitTermination(1L, TimeUnit.SECONDS)) {
                System.out.println("線程池中還有任務(wù)在執(zhí)行计露,當(dāng)前時(shí)間:" + sdf.format(new Date()));
            }

            System.out.println("線程池中已經(jīng)沒有在執(zhí)行的任務(wù)博脑,線程池已完全關(guān)閉憎乙!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image

當(dāng)調(diào)用shutdownNow()后,三個(gè)執(zhí)行的任務(wù)都被interrupt了叉趣。而且awaitTermination(1L, TimeUnit.SECONDS)返回的都是true泞边。

測(cè)試submit(Callable<T> task)方法

package OSChina.Client;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class CallableTask implements Callable{
    @Override
    public Object call() throws Exception {
        TimeUnit.SECONDS.sleep(5L);
        return "success";
    }
}

package OSChina.Client;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Client12 {
    public static void main(String[] args) {
        ExecutorService threadpool = null;
        threadpool = Executors.newFixedThreadPool(3);
        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("提交一個(gè)callable任務(wù)到線程池,現(xiàn)在時(shí)間是:" + sdf.format(new Date()));
        Future<String> future = threadpool.submit(new CallableTask());
        try {
            System.out.println("獲取callable任務(wù)的結(jié)果:" + future.get() + "君账,現(xiàn)在時(shí)間是:" + sdf.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            if(threadpool!=null){
                threadpool.shutdown();
            }
        }
    }
}

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末繁堡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乡数,更是在濱河造成了極大的恐慌椭蹄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件净赴,死亡現(xiàn)場(chǎng)離奇詭異绳矩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玖翅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門翼馆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人金度,你說我怎么就攤上這事应媚。” “怎么了猜极?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵中姜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我跟伏,道長(zhǎng)丢胚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任受扳,我火速辦了婚禮携龟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勘高。我一直安慰自己峡蟋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布华望。 她就那樣靜靜地躺著蕊蝗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪立美。 梳的紋絲不亂的頭發(fā)上匿又,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音建蹄,去河邊找鬼碌更。 笑死裕偿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痛单。 我是一名探鬼主播嘿棘,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旭绒!你這毒婦竟也來了鸟妙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤挥吵,失蹤者是張志新(化名)和其女友劉穎重父,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忽匈,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡房午,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丹允。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郭厌。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雕蔽,靈堂內(nèi)的尸體忽然破棺而出折柠,到底是詐尸還是另有隱情,我是刑警寧澤批狐,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布扇售,位于F島的核電站,受9級(jí)特大地震影響贾陷,放射性物質(zhì)發(fā)生泄漏缘眶。R本人自食惡果不足惜嘱根,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一髓废、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧该抒,春花似錦慌洪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至欧引,卻和暖如春频伤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芝此。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工憋肖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留因痛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓岸更,卻偏偏與公主長(zhǎng)得像鸵膏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怎炊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 平時(shí)工作中經(jīng)常碰到個(gè)各種多線程,有時(shí)候搞不清它們之間到底有什么區(qū)別瓜挽,這次來個(gè)總體的總結(jié)攀操,主要是以下這些:Execu...
    一只好奇的茂閱讀 2,337評(píng)論 1 35
  • ??一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程秸抚。當(dāng)一個(gè)程序運(yùn)行時(shí)速和,內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順...
    OmaiMoon閱讀 1,677評(píng)論 0 12
  • 為什么要用線程池 關(guān)于為什么要使用多線程剥汤,請(qǐng)參考【多線程與并發(fā)】:線程的創(chuàng)建颠放、狀態(tài)、方法中的最后一點(diǎn)吭敢。 那為什么要...
    maxwellyue閱讀 550評(píng)論 0 1
  • Sigurd溪步道(Sigurd Creek Trail)位于Squamish的Ashlu溪谷碰凶,其中通往Crook...
    游閑溫哥華閱讀 575評(píng)論 1 4
  • 【陽光男孩 張文哲 8月3日 晴 堅(jiān)持原創(chuàng)分享第276天】 “呼”一陣風(fēng)吹來,把這朵花吹得東倒西歪鹿驼,它大發(fā)...
    張文哲閱讀 380評(píng)論 3 3