JavaRush /Java Blog /Random-TW /你不能用線程毀掉 Java:第四部分 - Callable、Future 和朋友
Viacheslav
等級 3

你不能用線程毀掉 Java:第四部分 - Callable、Future 和朋友

在 Random-TW 群組發布

介紹

我們已經在第一部分中了解如何建立線程。讓我們再回憶一下。 你不能用線程破壞 Java:第四部分 - Callable、Future 和朋友 - 1線程是Thread在其中運行的東西run,所以讓我們使用tutorialspoint java在線編譯器並執行以下程式碼:
public class HelloWorld {

    public static void main(String []args){
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
這是在執行緒中運行任務的唯一選擇嗎?

java.util.concurrent.Callable

原來java.lang.Runnable有一個兄弟,他的名字叫java.util.concurrent.Callable,他出生在Java 1.5。有什麼區別?如果我們仔細查看該介面的 JavaDoc,我們會發現,與 不同的是Runnable,新介面聲明了一個call傳回結果的方法。另外,預設情況下它會拋出異常。也就是說,它使我們無需為try-catch受檢查的異常編寫區塊。已經不錯了,對吧?現在我們有Runnable一個新任務:
Callable task = () -> {
	return "Hello, World!";
};
但該怎麼辦呢?為什麼我們甚至需要一個在傳回結果的執行緒上執行的任務?顯然,將來我們期望收到將來執行的操作的結果。英語中的未來 - 未來。還有一個同名的介面:java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Future介面描述了一個用於處理我們計劃在將來獲取結果的任務的 API:獲取結果的方法、檢查狀態的方法。我們對其實作java.util.concurrent.FutureTaskFuture感興趣。也就是說,這就是將在 中執行的內容。此實現的另一個有趣之處在於它實現了 和。您可以將其視為在線程中處理任務的舊模型和新模型(在 java 1.5 中出現的意義上的新模型)的適配器。這是一個例子: TaskFutureRunnable
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class HelloWorld {

    public static void main(String []args) throws Exception {
        Callable task = () -> {
            return "Hello, World!";
        };
        FutureTask<String> future = new FutureTask<>(task);
        new Thread(future).start();
        System.out.println(future.get());
    }
}
從例子中可以看出,使用該方法我們得到了get問題的結果task(!)重要的,在使用該方法獲得結果的那一刻,get執行變得同步。您認為這裡會使用什麼機制?沒錯,沒有同步區塊 - 因此我們在 JVisualVM 中看到的WAITING不是作為monitorwait,而是作為完全相同的一個park(因為使用了該機制LockSupport)。

功能介面

接下來我們將討論 Java 1.8 中的類,因此做一個簡單的介紹是很有用的。我們看下面的程式碼:
Supplier<String> supplier = new Supplier<String>() {
	@Override
	public String get() {
		return "String";
	}
};
Consumer<String> consumer = new Consumer<String>() {
	@Override
	public void accept(String s) {
		System.out.println(s);
	}
};
Function<String, Integer> converter = new Function<String, Integer>() {
	@Override
	public Integer apply(String s) {
		return Integer.valueOf(s);
	}
};
有很多不必要的程式碼,不是嗎?每個聲明的類別都執行一個功能,但為了描述它,我們使用了一堆不必要的輔助程式碼。Java 開發人員也這麼認為。因此,他們引入了一組「函數式介面」(@FunctionalInterface),並決定現在 Java 本身將為我們「想出」一切,除了重要的:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- 提供者。它沒有參數,但它返回一些東西,也就是說,它提供它。 Consumer- 消費者。它接受某些東西作為輸入(參數)並用它做一些事情,即消耗一些東西。還有另一個功能。它接受某些內容作為輸入(參數s),執行某些操作並傳回某些內容。正如我們所看到的,泛型被積極使用。如果你不確定,你可以記住它們並閱讀「Java中泛型的理論或如何在實踐中放置括號」。

完整的未來

隨著時間的推移,Java 1.8 引進了一個名為 的新類別CompletableFuture。它實現了該接口Future,這意味著我們的接口task將在將來被執行,我們可以執行get並獲得結果。但他也實現了一些CompletionStage。從翻譯來看,它的目的已經很清楚了:它是某種計算的某個階段。該主題的簡要介紹可以在概述「 CompletionStage 和 CompletableFuture 簡介」中找到。我們直接進入正題吧。讓我們來看看可用的靜態方法清單來幫助我們開始: 你不能用線程破壞 Java:第四部分 - Callable、Future 和朋友 - 2以下是使用它們的選項:
import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String []args) throws Exception {
        // CompletableFuture уже содержащий результат
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Просто meaning");
        // CompletableFuture, запускающий (run) новый поток с Runnable, поэтому он Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // CompletableFuture, запускающий новый поток, результат которого возьмём у Supplier
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Значение";
        });
    }
}
如果我們執行這段程式碼,我們會看到創建CompletableFuture意味著整個鏈的啟動。因此,雖然與 Java8 中的 SteamAPI 有一些相似之處,但這就是這些方法之間的差異。例如:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
這是 Java 8 Stream Api 的範例(您可以在此處閱讀更多相關資訊「圖片和範例中的 Java 8 Stream API 指南」)。如果運行此程式碼,它將Executed不會顯示。也就是說,當在 Java 中建立流時,流不會立即啟動,而是等待,直到需要從中取得值。但CompletableFuture它立即啟動鏈執行,而不等待它被要求計算值。我認為理解這一點很重要。所以我們有CompletableFuture。我們如何創造一條鏈,我們有什麼手段?讓我們記住我們之前寫過的函數式介面。
  • 我們有一個函數 ( Function),它接受 A 並傳回 B。它只有一個方法 - apply(apply)。
  • 我們有一個消費者 ( Consumer),它接受 A 且不回傳任何內容 ( Void )。它只有一種方法── accept(接受)。
  • Runnable我們的程式碼在不接受或返回的線程上運行。它有一個方法 - run(運行)。
要記住的第二件事是,CompletalbeFuture它在工作中使用Runnable消費者和函數。有鑑於此,您可以始終記住您CompletableFuture可以這樣做:
public static void main(String []args) throws Exception {
        AtomicLong longValue = new AtomicLong(0);
        Runnable task = () -> longValue.set(new Date().getTime());
        Function<Long, Date> dateConverter = (longvalue) -> new Date(longvalue);
        Consumer<Date> printer = date -> {
            System.out.println(date);
            System.out.flush();
        };
        // CompletableFuture computation
        CompletableFuture.runAsync(task)
                         .thenApply((v) -> longValue.get())
                         .thenApply(dateConverter)
                         .thenAccept(printer);
}
方法thenRunthenApply版本thenAccept。_ Async_ 這意味著這些階段將在新線程中執行。它將會從一個特殊的池中取出,因此事先不知道它將是什麼樣的流量,新的還是舊的。這完全取決於任務的難度。除了這些方法之外,還有三種更有趣的可能性。為了清楚起見,讓我們想像一下我們有一個特定的服務從某個地方接收訊息並且需要時間:
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
現在,讓我們看看 的其他功能CompletableFuture。我們可以將結果CompletableFuture與另一個結果結合CompletableFuture
Supplier newsSupplier = () -> NewsService.getMessage();

CompletableFuture<String> reader = CompletableFuture.supplyAsync(newsSupplier);
CompletableFuture.completedFuture("!!")
				 .thenCombine(reader, (a, b) -> b + a)
				 .thenAccept(result -> System.out.println(result))
				 .get();
值得注意的是,預設情況下,線程將是守護線程,因此為了清楚起見get,我們使用等待結果。而且我們不僅可以組合(combine),還可以返回CompletableFuture
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
這裡我要指出的是,為了簡潔起見,使用了這個方法CompletableFuture.completedFuture。此方法不會建立新線程,因此鏈的其餘部分將在呼叫它的相同線程中執行completedFuture。還有一個方法thenAcceptBoth。它與 非常相似accept,但如果thenAccept它接受consumer,那麼thenAcceptBoth它接受另一個CompletableStage+作為輸入BiConsumer,即consumer,它接受 2 個來源作為輸入,而不是一個。這個詞還有另一種有趣的可能性Either你不能用線程毀掉 Java:第四部分 - Callable、Future 和朋友 - 3這些方法接受替代方法CompletableStage,並將在CompletableStage第一個執行的方法上執行。我想用另一個有趣的功能CompletableFuture——錯誤處理來結束這篇評論。
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
這段程式碼不會做任何事情,因為...... 將拋出異常並且什麼也不會發生。但如果我們取消註釋exceptionally,那麼我們就定義了行為。CompletableFuture我還建議觀看以下 有關此主題的影片: 以我的拙見,這些影片是網路上最直觀的影片之一。他們應該清楚這一切是如何運作的、我們擁有什麼武器庫以及為什麼需要這一切。

結論

希望現在已經清楚如何使用執行緒在計算後檢索計算結果。附加資料: #維亞切斯拉夫
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION