pengenalan
Dalam
Bahagian I , kami menyemak cara urutan dibuat. Mari kita ingat sekali lagi.
![Lebih baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan - 1]()
Benang diwakili oleh kelas Benang, yang
run()
kaedahnya dipanggil. Jadi mari kita gunakan
pengkompil Java dalam talian Tutorialspoint dan laksanakan kod berikut:
public class HelloWorld {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello World");
};
new Thread(task).start();
}
}
Adakah ini satu-satunya pilihan untuk memulakan tugasan pada urutan?
java.util.concurrent.Boleh dipanggil
Ternyata
java.lang.Runnable mempunyai saudara bernama
java.util.concurrent.Callable yang datang ke dunia dalam Java 1.5. Apakah perbezaannya? Jika anda melihat dengan teliti pada Javadoc untuk antara muka ini, kami melihat bahawa, tidak seperti
Runnable
, antara muka baharu mengisytiharkan
call()
kaedah yang mengembalikan hasil. Juga, ia membuang Exception secara lalai. Iaitu, ia menyelamatkan kita daripada perlu
try-catch
menyekat pengecualian yang diperiksa. Tidak teruk, bukan? Kini kami mempunyai tugas baharu dan bukannya
Runnable
:
Callable task = () -> {
return "Hello, World!";
};
Tetapi apa yang kita lakukan dengannya? Mengapakah kita memerlukan tugas yang dijalankan pada benang yang mengembalikan hasil? Jelas sekali, untuk sebarang tindakan yang dilakukan pada masa hadapan, kami menjangkakan akan menerima hasil daripada tindakan tersebut pada masa hadapan. Dan kami mempunyai antara muka dengan nama yang sepadan:
java.util.concurrent.Future
java.util.concurrent.Future
Antara muka
java.util.concurrent.Future mentakrifkan API untuk bekerja dengan tugasan yang keputusannya kami rancang untuk terima pada masa hadapan: kaedah untuk mendapatkan hasil dan kaedah untuk menyemak status. Berkenaan dengan
Future
, kami berminat dengan pelaksanaannya dalam kelas
java.util.concurrent.FutureTask . Ini ialah "Tugas" yang akan dilaksanakan dalam
Future
. Apa yang menjadikan pelaksanaan ini lebih menarik ialah ia turut melaksanakan Runnable. Anda boleh menganggap ini sejenis penyesuai antara model lama bekerja dengan tugasan pada benang dan model baharu (baru dalam erti kata ia muncul dalam Java 1.5). Berikut adalah contoh:
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());
}
}
Seperti yang anda lihat daripada contoh, kami menggunakan
get
kaedah untuk mendapatkan hasil daripada tugasan.
Catatan:apabila anda mendapat keputusan menggunakan
get()
kaedah, pelaksanaan menjadi segerak! Apakah mekanisme yang anda fikir akan digunakan di sini? Benar, tiada blok penyegerakan. Itulah sebabnya kita tidak akan melihat
MENUNGGU dalam JVisualVM sebagai
monitor
atau
wait
, tetapi sebagai kaedah biasa
park()
(kerana
LockSupport
mekanisme sedang digunakan).
Antara muka berfungsi
Seterusnya, kita akan bercakap tentang kelas dari Java 1.8, jadi kita patut memberikan pengenalan ringkas. Lihat kod berikut:
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);
}
};
Banyak dan banyak kod tambahan, bukankah anda katakan? Setiap kelas yang diisytiharkan melaksanakan satu fungsi, tetapi kami menggunakan sekumpulan kod sokongan tambahan untuk mentakrifkannya. Dan ini adalah bagaimana pemaju Java berfikir. Sehubungan itu, mereka memperkenalkan satu set "antara muka berfungsi" (
@FunctionalInterface
) dan memutuskan bahawa kini Java sendiri akan melakukan "pemikiran", hanya meninggalkan perkara penting untuk kita risaukan:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
A
Supplier
bekalan. Ia tidak mempunyai parameter, tetapi ia mengembalikan sesuatu. Ini adalah bagaimana ia membekalkan sesuatu. A
Consumer
memakan. Ia mengambil sesuatu sebagai input (hujah) dan melakukan sesuatu dengannya. Hujah adalah apa yang ia makan. Kemudian kami juga mempunyai
Function
. Ia mengambil input (hujah), melakukan sesuatu, dan mengembalikan sesuatu. Anda dapat melihat bahawa kami secara aktif menggunakan generik. Jika anda tidak pasti, anda boleh mendapatkan penyegar semula dengan membaca "
Generics in Java: how to use angled brackets in practice ".
CompletableFuture
Masa berlalu dan kelas baharu yang dipanggil
CompletableFuture
muncul di Java 1.8. Ia melaksanakan
Future
antara muka, iaitu tugas kami akan diselesaikan pada masa hadapan, dan kami boleh menghubungi
get()
untuk mendapatkan hasilnya. Tetapi ia juga melaksanakan
CompletionStage
antara muka. Nama menyatakan semuanya: ini adalah peringkat tertentu bagi beberapa set pengiraan. Pengenalan ringkas kepada topik boleh didapati dalam ulasan di sini: Pengenalan kepada CompletionStage dan CompletableFuture. Mari kita teruskan kepada intipati. Mari lihat senarai kaedah statik yang tersedia yang akan membantu kami bermula:
![Lebih baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan - 2]()
Berikut ialah pilihan untuk menggunakannya:
import java.util.concurrent.CompletableFuture;
public class App {
public static void main(String[] args) throws Exception {
// A CompletableFuture that already contains a Result
CompletableFuture<String> completed;
completed = CompletableFuture.completedFuture("Just a value");
// A CompletableFuture that runs a new thread from Runnable. That's why it's Void
CompletableFuture<Void> voidCompletableFuture;
voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("run " + Thread.currentThread().getName());
});
// A CompletableFuture that starts a new thread whose result we'll get from a Supplier
CompletableFuture<String> supplier;
supplier = CompletableFuture.supplyAsync(() -> {
System.out.println("supply " + Thread.currentThread().getName());
return "Value";
});
}
}
Jika kita melaksanakan kod ini, kita akan melihat bahawa mencipta a
CompletableFuture
juga melibatkan pelancaran keseluruhan saluran paip. Oleh itu, dengan persamaan tertentu dengan SteamAPI dari Java8, di sinilah kita dapati perbezaan antara pendekatan ini. Sebagai contoh:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
System.out.println("Executed");
return value.toUpperCase();
});
Ini ialah contoh API Stream Java 8. Jika anda menjalankan kod ini, anda akan melihat bahawa "Dilaksanakan" tidak akan dipaparkan. Dalam erti kata lain, apabila strim dicipta di Jawa, strim tidak bermula serta-merta. Sebaliknya, ia menunggu seseorang mahukan nilai daripadanya. Tetapi
CompletableFuture
mula melaksanakan saluran paip dengan serta-merta, tanpa menunggu seseorang meminta nilainya. Saya rasa ini penting untuk difahami. S o, kami mempunyai
CompletableFuture
. Bagaimanakah kita boleh membuat saluran paip (atau rantai) dan apakah mekanisme yang kita ada? Ingat antara muka berfungsi yang kami tulis sebelum ini.
- Kami mempunyai
Function
yang mengambil A dan mengembalikan B. Ia mempunyai satu kaedah: apply()
.
- Kami mempunyai
Consumer
yang mengambil A dan tidak mengembalikan apa-apa (Batal). Ia mempunyai satu kaedah: accept()
.
- Kami mempunyai
Runnable
, yang berjalan pada benang, dan tidak mengambil apa-apa dan tidak mengembalikan apa-apa. Ia mempunyai satu kaedah: run()
.
Perkara seterusnya yang perlu diingat ialah
CompletableFuture
menggunakan
Runnable
,
Consumers
, dan
Functions
dalam kerjanya. Oleh itu, anda sentiasa boleh mengetahui bahawa anda boleh melakukan perkara berikut dengan
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);
}
Kaedah
thenRun()
,
thenApply()
, dan
thenAccept()
mempunyai versi "Async". Ini bermakna bahawa peringkat ini akan diselesaikan pada urutan yang berbeza. Urutan ini akan diambil daripada kumpulan khas — jadi kami tidak akan mengetahui terlebih dahulu sama ada ia akan menjadi benang baharu atau lama. Semuanya bergantung pada seberapa intensif pengiraan tugas itu. Sebagai tambahan kepada kaedah ini, terdapat tiga lagi kemungkinan yang menarik. Untuk kejelasan, mari bayangkan bahawa kami mempunyai perkhidmatan tertentu yang menerima beberapa jenis mesej dari suatu tempat — dan ini memerlukan masa:
public static class NewsService {
public static String getMessage() {
try {
Thread.currentThread().sleep(3000);
return "Message";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Sekarang, mari kita lihat kebolehan lain yang
CompletableFuture
menyediakan. Kita boleh menggabungkan hasil a
CompletableFuture
dengan hasil yang lain
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();
Ambil perhatian bahawa benang adalah benang daemon secara lalai, jadi untuk kejelasan, kami gunakan
get()
untuk menunggu hasilnya. Bukan sahaja kita boleh menggabungkan
CompletableFutures
, kita juga boleh mengembalikan
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
Di sini saya ingin ambil perhatian bahawa
CompletableFuture.completedFuture()
kaedah itu digunakan untuk ringkas. Kaedah ini tidak mencipta utas baharu, jadi saluran paip yang lain akan dilaksanakan pada utas yang sama tempat
completedFuture
dipanggil. Terdapat juga
thenAcceptBoth()
kaedah. Ia sangat serupa dengan
accept()
, tetapi jika
thenAccept()
menerima a
Consumer
,
thenAcceptBoth()
menerima satu lagi
CompletableStage
+
BiConsumer
sebagai input, iaitu a
consumer
yang mengambil 2 sumber dan bukannya satu. Terdapat satu lagi keupayaan menarik yang ditawarkan oleh kaedah yang namanya termasuk perkataan "Sama ada":
![Lebih baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan - 3]()
Kaedah ini menerima alternatif
CompletableStage
dan dilaksanakan pada yang
CompletableStage
dilaksanakan terlebih dahulu. Akhir sekali, saya ingin menamatkan ulasan ini dengan satu lagi ciri menarik
CompletableFuture
: pengendalian ralat.
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
//.exceptionally(ex -> 0L)
.thenAccept(val -> System.out.println(val));
Kod ini tidak akan melakukan apa-apa, kerana akan ada pengecualian dan tiada perkara lain yang akan berlaku. Tetapi dengan membatalkan ulasan pernyataan "luar biasa", kami mentakrifkan tingkah laku yang dijangkakan. Bercakap tentang
CompletableFuture
, saya juga mengesyorkan anda menonton video berikut:
Pada pendapat saya, ini adalah antara video yang paling menerangkan di Internet. Mereka harus menjelaskan cara ini semua berfungsi, alat alat yang kami sediakan, dan mengapa semua ini diperlukan.
Kesimpulan
Mudah-mudahan, kini jelas cara anda boleh menggunakan benang untuk mendapatkan pengiraan selepas selesai. Bahan tambahan:
Lebih baik bersama: Java dan kelas Thread. Bahagian I — Benang pelaksanaan Lebih baik bersama: Java dan kelas Benang. Bahagian II — Penyegerakan Lebih baik bersama: Java dan kelas Thread. Bahagian III — Interaksi Lebih Baik bersama: Java dan kelas Thread. Bahagian V — Pelaksana, ThreadPool, Fork/Join Better together: Java dan kelas Thread. Bahagian VI - Jauhkan api!
GO TO FULL VERSION