pengenalan
Kami telah melihat bagaimana benang dicipta pada
bahagian pertama . Mari kita ingat lagi.
Benang ialah
Thread
sesuatu yang berjalan di dalamnya
run
, jadi mari gunakan
pengkompil dalam talian tutorialspoint java 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 menjalankan tugas dalam urutan?
java.util.concurrent.Boleh dipanggil
Ternyata
java.lang.Runnable mempunyai saudara lelaki dan namanya
java.util.concurrent.Callable dan dia dilahirkan di Java 1.5. Apakah perbezaannya? Jika kita melihat lebih dekat pada JavaDoc antara muka ini, kita melihat bahawa, tidak seperti
Runnable
, antara muka baharu mengisytiharkan kaedah
call
yang mengembalikan hasil. Selain itu, secara lalai ia membuang Exception. Iaitu, ia menyelamatkan kita daripada keperluan untuk menulis
try-catch
blok untuk pengecualian yang diperiksa. Tak teruk dah kan? Sekarang kita mempunyai
Runnable
tugas baharu sebaliknya:
Callable task = () -> {
return "Hello, World!";
};
Tetapi apa yang perlu dilakukan dengannya? Mengapakah kita memerlukan tugas yang dijalankan pada benang yang mengembalikan hasil? Jelas sekali, pada masa hadapan kita mengharapkan untuk menerima hasil tindakan yang akan dilakukan pada masa hadapan. Masa Depan dalam Bahasa Inggeris - Masa Depan. Dan terdapat antara muka dengan nama yang sama:
java.util.concurrent.Future
java.util.concurrent.Future
Antara muka
java.util.concurrent.Future menerangkan API untuk bekerja dengan tugasan yang keputusannya kami rancang untuk dapatkan pada masa hadapan: kaedah untuk mendapatkan hasil, kaedah untuk menyemak status. Kami
Future
berminat dengan pelaksanaannya
java.util.concurrent.FutureTask . Iaitu
Task
, inilah yang akan dilaksanakan dalam
Future
. Apa yang menarik juga tentang pelaksanaan ini ialah ia melaksanakan dan
Runnable
. Anda boleh menganggap ini sejenis penyesuai model lama bekerja dengan tugasan dalam benang dan model baharu (baru dalam erti kata ia muncul dalam java 1.5). Berikut ialah 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 dapat dilihat dari contoh, menggunakan kaedah kita memperoleh
get
hasil daripada masalah
task
.
(!)Penting, bahawa pada masa ini keputusan diperoleh menggunakan kaedah,
get
pelaksanaan menjadi segerak. Apakah mekanisme yang anda fikir akan digunakan di sini? Betul, tiada blok penyegerakan - oleh itu kita akan melihat
MENUNGGU dalam JVisualVM bukan sebagai
monitor
atau
wait
, tetapi sebagai yang sama
park
(kerana mekanisme digunakan
LockSupport
).
Antara Muka Berfungsi
Seterusnya kita akan bercakap tentang kelas dari Java 1.8, jadi adalah berguna untuk membuat pengenalan ringkas. Mari 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);
}
};
Terdapat banyak kod yang tidak diperlukan, bukan? Setiap kelas yang diisytiharkan melaksanakan satu fungsi, tetapi untuk menerangkannya, kami menggunakan sekumpulan kod tambahan yang tidak diperlukan. Dan pembangun Java juga berpendapat demikian. Oleh itu, mereka memperkenalkan satu set "antara muka berfungsi" (
@FunctionalInterface
) dan memutuskan bahawa kini Java sendiri akan "memikirkan" segala-galanya untuk kami, kecuali yang penting:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier
- pembekal. Ia tidak mempunyai parameter, tetapi ia mengembalikan sesuatu, iaitu, ia membekalkannya.
Consumer
- pengguna. Ia mengambil sesuatu sebagai input (parameter s) dan melakukan sesuatu dengannya, iaitu, menggunakan sesuatu. Ada fungsi lain. Ia mengambil sesuatu sebagai input (parameter
s
), melakukan sesuatu dan mengembalikan sesuatu. Seperti yang kita lihat, generik digunakan secara aktif. Jika anda tidak pasti, anda boleh mengingatinya dan membaca "
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan ."
CompletableFuture
Seiring berjalannya waktu, Java 1.8 memperkenalkan kelas baharu yang dipanggil
CompletableFuture
. Ia melaksanakan antara muka
Future
, bermakna milik kami
task
akan dilaksanakan pada masa hadapan dan kami boleh melaksanakan
get
dan mendapatkan hasilnya. Tetapi dia juga melaksanakan beberapa
CompletionStage
. Daripada terjemahan tujuannya sudah jelas: ia adalah Tahap tertentu dari beberapa jenis pengiraan. Pengenalan ringkas kepada topik boleh didapati dalam gambaran keseluruhan "
Pengenalan kepada CompletionStage dan CompletableFuture ". Mari terus ke intinya. Mari lihat senarai kaedah statik yang tersedia untuk membantu kami bermula:
Berikut ialah pilihan untuk menggunakannya:
import java.util.concurrent.CompletableFuture;
public class App {
public static void main(String []args) throws Exception {
CompletableFuture<String> completed;
completed = CompletableFuture.completedFuture("Просто meaning");
CompletableFuture<Void> voidCompletableFuture;
voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("run " + Thread.currentThread().getName());
});
CompletableFuture<String> supplier;
supplier = CompletableFuture.supplyAsync(() -> {
System.out.println("supply " + Thread.currentThread().getName());
return "Значение";
});
}
}
Jika kita menjalankan kod ini, kita akan melihat bahawa penciptaan
CompletableFuture
melibatkan memulakan keseluruhan rantai. Oleh itu, walaupun terdapat beberapa persamaan dengan SteamAPI dari Java8, ini adalah 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 Java 8 Stream Api (anda boleh membaca lebih lanjut mengenainya di sini "
Panduan API Java 8 Stream dalam Gambar dan Contoh "). Jika anda menjalankan kod ini, ia
Executed
tidak akan dipaparkan. Iaitu, apabila mencipta aliran di Jawa, aliran tidak bermula serta-merta, tetapi menunggu sehingga nilai diperlukan daripadanya. Tetapi
CompletableFuture
ia memulakan rantaian untuk pelaksanaan serta-merta, tanpa menunggu ia diminta untuk nilai yang dikira. Saya rasa penting untuk memahami perkara ini. Jadi kami mempunyai CompletableFuture. Bagaimanakah kita boleh membuat rantaian dan apakah cara yang kita ada? Mari kita ingat tentang antara muka berfungsi yang kami tulis sebelum ini.
- Kami mempunyai fungsi (
Function
) yang mengambil A dan mengembalikan B. Ia mempunyai satu kaedah - apply
(gunakan).
- Kami mempunyai pengguna (
Consumer
) yang menerima A dan tidak mengembalikan apa-apa ( Void ). Ia hanya mempunyai satu kaedah - accept
(terima).
- Kami mempunyai kod berjalan pada benang
Runnable
yang tidak menerima atau kembali. Ia mempunyai satu kaedah - run
(lari).
Perkara kedua yang perlu diingat ialah
CompletalbeFuture
dalam kerjanya ia menggunakan
Runnable
pengguna dan fungsi. Memandangkan ini, anda sentiasa boleh ingat bahawa anda
CompletableFuture
boleh melakukan ini:
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.runAsync(task)
.thenApply((v) -> longValue.get())
.thenApply(dateConverter)
.thenAccept(printer);
}
Kaedah
thenRun
mempunyai
thenApply
versi .
thenAccept
_
Async
Ini bermakna bahawa peringkat ini akan dilaksanakan dalam urutan baharu. Ia akan diambil dari kolam khas, jadi tidak diketahui terlebih dahulu jenis alirannya, baru atau lama. Semuanya bergantung pada betapa sukarnya tugas itu. Sebagai tambahan kepada kaedah ini, terdapat tiga lagi kemungkinan yang menarik. Untuk kejelasan, mari bayangkan bahawa kami mempunyai perkhidmatan tertentu yang menerima mesej dari suatu tempat dan 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 ciri-ciri lain yang
CompletableFuture
. Kita boleh menggabungkan hasil
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();
Perlu diingat bahawa secara lalai benang akan menjadi benang daemon, jadi untuk kejelasan
get
, kita gunakan untuk menunggu hasilnya. Dan kita bukan sahaja boleh menggabungkan (menggabungkan), tetapi juga mengembalikan
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
Di sini saya ingin ambil perhatian bahawa untuk ringkasnya, kaedah itu digunakan
CompletableFuture.completedFuture
. Kaedah ini tidak mencipta utas baharu, jadi rantaian selebihnya akan dilaksanakan dalam utas yang sama di mana ia dipanggil
completedFuture
. Terdapat juga kaedah
thenAcceptBoth
. Ia sangat serupa dengan
accept
, tetapi jika
thenAccept
ia menerima
consumer
, maka
thenAcceptBoth
ia menerima satu lagi
CompletableStage
+ sebagai input
BiConsumer
, iaitu
consumer
, yang menerima 2 sumber sebagai input, bukan satu. Terdapat satu lagi kemungkinan menarik dengan perkataan
Either
:
Kaedah ini menerima alternatif
CompletableStage
dan akan dilaksanakan pada kaedah
CompletableStage
yang dilaksanakan dahulu. Dan saya ingin menyelesaikan ulasan ini dengan satu lagi ciri menarik
CompletableFuture
- pengendalian ralat.
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
.thenAccept(val -> System.out.println(val));
Kod ini tidak akan melakukan apa-apa, kerana... pengecualian akan dilemparkan dan tiada apa yang akan berlaku. Tetapi jika kita tidak mengulas
exceptionally
, maka kita menentukan tingkah laku.
CompletableFuture
Saya juga mengesyorkan menonton video berikut mengenai topik ini :
Pada pendapat saya, video ini adalah antara yang paling visual di Internet. Ia harus jelas daripada mereka bagaimana ia berfungsi, senjata apa yang kita ada dan mengapa ia semua diperlukan.
Kesimpulan
Mudah-mudahan kini jelas bagaimana benang boleh digunakan untuk mendapatkan semula pengiraan selepas ia dikira. Bahan tambahan:
#Viacheslav
GO TO FULL VERSION