成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java 8 并發(fā): Threads 和 Executors

J4ck_Chan / 1562人閱讀

摘要:能夠異步的執(zhí)行任務(wù),并且通常管理一個線程池。這樣我們就不用手動的去創(chuàng)建線程了,線程池中的所有線程都將被重用。在之后不能再提交任務(wù)到線程池。它不使用固定大小的線程池,默認(rèn)情況下是主機(jī)的可用內(nèi)核數(shù)。

原文地址: Java 8 Concurrency Tutorial: Threads and Executors

Java 5 初次引入了Concurrency API,并在隨后的發(fā)布版本中不斷優(yōu)化和改進(jìn)。這篇文章的大部分概念也適用于老的版本。我的代碼示例主要聚焦在Java 8上,并大量適用 lambda 表達(dá)式和一些新特性。如果你還不熟悉 lambda 表達(dá)式,建議先閱讀 Java 8 Tutorial。

ThreadsRunnables

所有現(xiàn)代操作系統(tǒng)都是通過進(jìn)程線程來支持并發(fā)的。進(jìn)程通常是相互獨(dú)立運(yùn)行的程序?qū)嵗?。例如,你啟動一個 Java 程序,操作系統(tǒng)會產(chǎn)生一個新的進(jìn)程和其他程序并行運(yùn)行。在這些進(jìn)程中可以利用線程同時執(zhí)行代碼。這樣我們就可以充分利用 CPU

JavaJDK 1.0 開始就支持線程。在開始一個新線程之前,必須先指定運(yùn)行的代碼,通常稱為 Task。下面是通過實(shí)現(xiàn) Runnable 接口來啟動一個新線程的例子:

Runnable task = () -> {
    String threadName = Thread.currentThread().getName();
    System.out.println("Hello " + threadName);
};

task.run();

Thread thread = new Thread(task);
thread.start();

System.out.println("Done!");

由于 Runnable 是一個函數(shù)式接口,我們可以使用 lambda 表達(dá)式來打印線程的名字到控制臺。我們直接在主線程上執(zhí)行Runnable,然后開始一個新線程。在控制臺你將看到這樣的結(jié)果:

Hello main
Hello Thread-0
Done!

或者:

Hello main
Done!
Hello Thread-0

由于是并發(fā)執(zhí)行,我們無法預(yù)測 Runnable 是在打印 Done 之前還是之后調(diào)用,順序不是不確定的,因此并發(fā)編程成為大型應(yīng)用程序開發(fā)中一項(xiàng)復(fù)雜的任務(wù)。

線程也可以休眠一段時間,例如下面的例子:

Runnable runnable = () -> {
    try {
        String name = Thread.currentThread().getName();
        System.out.println("Foo " + name);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Bar " + name);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Thread thread = new Thread(runnable);
thread.start();

執(zhí)行上面的代碼會在兩個打印語句之間停留1秒鐘。TimeUnit 是一個時間單位的枚舉,或者可以通過調(diào)用 Thread.sleep(1000) 實(shí)現(xiàn)。

使用 Thread 類可能非常繁瑣且容易出錯。由于這個原因,在2004年,Java 5版本引入了 Concurrency APIAPI 位于 java.util.concurrent 包下,包含了許多有用的有關(guān)并發(fā)編程的類。從那時起,每個新發(fā)布的 Java 版本都增加了并發(fā) APIJava 8 也提供了新的類和方法來處理并發(fā)。

現(xiàn)在我們來深入了解一下Concurrency API中最重要的部分 - executor services

Executors

Concurrency API 引入了 ExecutorService 的概念,作為處理線程的高級別方式用來替代 Threads。 Executors 能夠異步的執(zhí)行任務(wù),并且通常管理一個線程池。這樣我們就不用手動的去創(chuàng)建線程了,線程池中的所有線程都將被重用。從而可以在一個
executor service 的整個應(yīng)用程序生命周期中運(yùn)行盡可能多的并發(fā)任務(wù)。

下面是一個簡單的 executors 例子:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
    String threadName = Thread.currentThread().getName();
    System.out.println("Hello " + threadName);
});

// => Hello pool-1-thread-1

Executors 類提供了方便的工廠方法來創(chuàng)建不同類型的 executor services 。在這個例子中使用了只執(zhí)行一個線程的 executor

執(zhí)行結(jié)果看起來和上面的示例類似,但是你會注意到一個重要區(qū)別:Java 進(jìn)程永遠(yuǎn)不會停止,執(zhí)行者必須明確的停止它,否則它會不斷的接受新的任務(wù)。

ExecutorService 為此提供了兩種方法:shutdown() 等待當(dāng)前任務(wù)執(zhí)行完畢,而 shutdownNow() 則中斷所有正在執(zhí)行的任務(wù),并立即關(guān)閉執(zhí)行程序。在 shudown 之后不能再提交任務(wù)到線程池。

下面是我關(guān)閉程序的首選方式:

try {
    System.out.println("attempt to shutdown executor");
    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
    System.err.println("tasks interrupted");
}
finally {
    if (!executor.isTerminated()) {
        System.err.println("cancel non-finished tasks");
    }
    executor.shutdownNow();
    System.out.println("shutdown finished");
}

執(zhí)行者調(diào)用 shutdown 關(guān)閉 executor,在等待 5 秒鐘鐘后,不管任務(wù)有沒有執(zhí)行完畢都調(diào)用 shutdownNow 中斷正在執(zhí)行的任務(wù)而關(guān)閉。

Callables 和 Futures

除了 Runnable 以外,executors 還支持 Callable 任務(wù),和 Runnable 一樣是一個函數(shù)式接口,但它是有返回值的。

下面是一個使用 lambda 表達(dá)式定義的 Callable ,在睡眠 1 秒后返回一個整形值。

Callable task = () -> {
    try {
        TimeUnit.SECONDS.sleep(1);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
};

Runnable 一樣,Callable 也可以提交到 executor services,但是執(zhí)行的結(jié)果是什么?由于 submit() 不等待任務(wù)執(zhí)行完成,executor service 不能直接返回調(diào)用的結(jié)果。相對應(yīng)的,它返回一個 Future 類型的結(jié)果,使用 Future 可以檢索實(shí)際執(zhí)行結(jié)果。

ExecutorService executor = Executors.newFixedThreadPool(1);
Future future = executor.submit(task);

System.out.println("future done? " + future.isDone());

Integer result = future.get();

System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);

在將 Callable 提交給 executor 后,首先通過 isDone() 來檢查 future 是否執(zhí)行完畢。我敢肯定,情況并非如此,因?yàn)樯厦娴恼{(diào)用在返回整數(shù)之前睡眠了 1 秒鐘。

調(diào)用方法 get() 會阻塞當(dāng)前線程,直到 callable 執(zhí)行完成返回結(jié)果,現(xiàn)在 future 執(zhí)行完成,并在控制臺輸出下面的結(jié)果:

future done? false
future done? true
result: 123

Futureexecutor service 緊密結(jié)合,如果關(guān)閉 executor service, 每個 Future 都會拋出異常。

executor.shutdownNow();
future.get();

這里創(chuàng)建 executor 的方式與前面的示例不同,這里使用 newFixedThreadPool(1) 來創(chuàng)建一個線程數(shù)量為 1 的線程池來支持 executor, 這相當(dāng)于 newSingleThreadExecutor() ,稍后我們我們會通過傳遞一個大于 1 的值來增加線程池的大小。

Timeouts

任何對 future.get()的調(diào)用都會阻塞并等待 Callable 被終止。 在最壞的情況下,一個可調(diào)用函數(shù)將永遠(yuǎn)運(yùn)行,從而使應(yīng)用程序無法響應(yīng)??梢院唵蔚赝ㄟ^超時來抵消這些情況:

ExecutorService executor = Executors.newFixedThreadPool(1);

Future future = executor.submit(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
});

future.get(1, TimeUnit.SECONDS);

執(zhí)行上面的代碼會拋出 TimeoutException

Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)

指定了 1 秒鐘的最長等待時間,但是在返回結(jié)果之前,可調(diào)用事實(shí)上需要 2 秒鐘的時間。

InvokeAll

Executors 支持通過 invokeAll() 批量提交多個 Callable 。這個方法接受一個 Callable 類型集合的參數(shù),并返回一個 Future 類型的 List。

ExecutorService executor = Executors.newWorkStealingPool();

List> callables = Arrays.asList(
        () -> "task1",
        () -> "task2",
        () -> "task3");

executor.invokeAll(callables)
    .stream()
    .map(future -> {
        try {
            return future.get();
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
    })
    .forEach(System.out::println);

在這個例子中,我們利用 Java 8 的流來處理 invokeAll 調(diào)用返回的所有 Future。 我們首先映射每個 Future 的返回值,然后將每個值打印到控制臺。 如果還不熟悉流,請閱讀Java 8 Stream Tutorial。

InvokeAny

批量提交可調(diào)用的另一種方法是 invokeAny(),它與 invokeAll() 略有不同。 該方法不會返回所有的 Future 對象,它只返回第一個執(zhí)行完畢任務(wù)的結(jié)果。

Callable callable(String result, long sleepSeconds) {
    return () -> {
        TimeUnit.SECONDS.sleep(sleepSeconds);
        return result;
    };
}

我們使用這種方法來創(chuàng)建一個有三個不同睡眠時間的 Callable。 通過 invokeAny()將這些可調(diào)用對象提交給 executor ,返回最快執(zhí)行完畢結(jié)果,在這種情況下,task2:

ExecutorService executor = Executors.newWorkStealingPool();

List> callables = Arrays.asList(
    callable("task1", 2),
    callable("task2", 1),
    callable("task3", 3));

String result = executor.invokeAny(callables);
System.out.println(result);

// => task2

上面的例子使用通過 newWorkStealingPool() 創(chuàng)建的另一種類型的 executor。 這個工廠方法是 Java 8 的一部分,并且返回一個類型為 ForkJoinPoolexecutor,它與正常的 executor 略有不同。 它不使用固定大小的線程池,默認(rèn)情況下是主機(jī)CPU的可用內(nèi)核數(shù)。

Scheduled Executors

我們已經(jīng)學(xué)會了如何在 Executors 上提交和運(yùn)行任務(wù)。 為了多次定期運(yùn)行任務(wù),我們可以使用 scheduled thread pools

ScheduledExecutorService 能夠安排任務(wù)定期運(yùn)行或在一段時間過后運(yùn)行一次。

下面代碼示例一個任務(wù)在三秒鐘后運(yùn)行:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture future = executor.schedule(task, 3, TimeUnit.SECONDS);

TimeUnit.MILLISECONDS.sleep(1337);

long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);

調(diào)度任務(wù)產(chǎn)生一個類型為 ScheduledFuture的值,除了 Future 之外,它還提供getDelay() 方法來檢索任務(wù)執(zhí)行的剩余時間。

為了定時執(zhí)行的任務(wù),executor 提供了兩個方法 scheduleAtFixedRate()
scheduleWithFixedDelay() 。 第一種方法能夠執(zhí)行具有固定時間間隔的任務(wù),例如, 每秒一次:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());

int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);

此外,此方法還可以設(shè)置延遲時間,該延遲描述了首次執(zhí)行任務(wù)之前的等待時間。

scheduleWithFixedDelay() 方法與 scheduleAtFixedRate() 略有不同,不同之處是它們的等待時間,scheduleWithFixedDelay() 的等待時間是在上一個任務(wù)結(jié)束和下一個任務(wù)開始之間施加的。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Scheduling: " + System.nanoTime());
    }
    catch (InterruptedException e) {
        System.err.println("task interrupted");
    }
};

executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);

本示例在執(zhí)行結(jié)束和下一次執(zhí)行開始之間延遲 1 秒。 初始延遲為 0,任務(wù)持續(xù)時間為 2 秒。 所以我們結(jié)束了一個0s,3s,6s,9s等的執(zhí)行間隔。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/70831.html

相關(guān)文章

  • Java 8 并發(fā)教程:線程執(zhí)行器

    摘要:在這個示例中我們使用了一個單線程線程池的。在延遲消逝后,任務(wù)將會并發(fā)執(zhí)行。這是并發(fā)系列教程的第一部分。第一部分線程和執(zhí)行器第二部分同步和鎖第三部分原子操作和 Java 8 并發(fā)教程:線程和執(zhí)行器 原文:Java 8 Concurrency Tutorial: Threads and Executors 譯者:BlankKelly 來源:Java8并發(fā)教程:Threads和Execut...

    jsdt 評論0 收藏0
  • Java多線程奇幻之旅——Synchronized方式CAS方式實(shí)現(xiàn)線程安全性能思考

    摘要:前言在上一篇文章中多線程奇幻之旅算法實(shí)現(xiàn)線程安全,我們介紹了和方式實(shí)現(xiàn)線程安全類的方法,兩種方式一個是鎖定阻塞方式,一個是非阻塞方式。 前言 在上一篇文章中《Java多線程奇幻之旅——CAS算法實(shí)現(xiàn)線程安全》,我們介紹了Synchronized和CAS方式實(shí)現(xiàn)線程安全類的方法,兩種方式一個是鎖定阻塞方式,一個是非阻塞方式。本文專注于兩種實(shí)現(xiàn)方式效率問題。本文是上篇文章的延續(xù),會借用到上...

    Chaz 評論0 收藏0
  • 小馬哥Java面試題課程總結(jié)

    摘要:無限期等待另一個線程執(zhí)行特定操作。線程安全基本版請說明以及的區(qū)別值都不能為空數(shù)組結(jié)構(gòu)上,通過數(shù)組和鏈表實(shí)現(xiàn)。優(yōu)先考慮響應(yīng)中斷,而不是響應(yīng)鎖的普通獲取或重入獲取。只是在最后獲取鎖成功后再把當(dāng)前線程置為狀態(tài)然后再中斷線程。 前段時間在慕課網(wǎng)直播上聽小馬哥面試勸退(面試虐我千百遍,Java 并發(fā)真討厭),發(fā)現(xiàn)講得東西比自己拿到offer還要高興,于是自己在線下做了一點(diǎn)小筆記,供各位參考。 課...

    FingerLiu 評論0 收藏0
  • [case39]聊聊jdk httpclient的executor

    摘要:序本文主要研究一下的這里如果的為,則會創(chuàng)建這里如果是的話,參數(shù)傳遞的是如果是同步的方法,則傳的值是這里創(chuàng)建了一個,然后調(diào)用,這里使用了可以看到這里使用的是的方法注意這個方法是才有的,也是在這里使用的由于默認(rèn)是使用創(chuàng)建的, 序 本文主要研究一下jdk httpclient的executor HttpClientImpl java.net.http/jdk/internal/net/htt...

    dabai 評論0 收藏0
  • Java 線程池的認(rèn)識使用

    摘要:用于限定中線程數(shù)的最大值。該線程池中的任務(wù)隊(duì)列維護(hù)著等待執(zhí)行的對象。線程池和消息隊(duì)列筆者在實(shí)際工程應(yīng)用中,使用過多線程和消息隊(duì)列處理過異步任務(wù)。以上是筆者在學(xué)習(xí)實(shí)踐之后對于多線程和消息隊(duì)列的粗淺認(rèn)識,初學(xué)者切莫混淆兩者的作用。 多線程編程很難,難點(diǎn)在于多線程代碼的執(zhí)行不是按照我們直覺上的執(zhí)行順序。所以多線程編程必須要建立起一個宏觀的認(rèn)識。 線程池是多線程編程中的一個重要概念。為了能夠更...

    mgckid 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<