Perkenalan
Kita telah melihat bagaimana thread dibuat di
bagian pertama . Mari kita ingat lagi.
Thread adalah
Thread
sesuatu yang berjalan di dalamnya
run
, jadi mari gunakan
compiler java online tutorialspoint dan jalankan kode berikut:
public class HelloWorld {
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello World");
};
new Thread(task).start();
}
}
Apakah ini satu-satunya pilihan untuk menjalankan tugas di thread?
java.util.bersamaan.Dapat Dipanggil
Ternyata
java.lang.Runnable mempunyai saudara laki-laki bernama
java.util.concurrent.Callable dan dia lahir di Java 1.5. Apa perbedaannya? Jika kita melihat lebih dekat pada JavaDoc antarmuka ini, kita melihat bahwa, tidak seperti
Runnable
, antarmuka baru mendeklarasikan metode
call
yang mengembalikan hasil. Juga, secara default ia memunculkan Pengecualian. Artinya, ini menyelamatkan kita dari kebutuhan untuk menulis
try-catch
blok untuk pengecualian yang dicentang. Sudah lumayan, kan? Sekarang kami memiliki
Runnable
tugas baru:
Callable task = () -> {
return "Hello, World!";
};
Tapi apa hubungannya dengan itu? Mengapa kita memerlukan tugas yang berjalan di thread yang memberikan hasil? Tentunya di masa depan kita mengharapkan hasil dari tindakan yang akan dilakukan di masa depan. Masa Depan dalam bahasa Inggris - Masa Depan. Dan ada antarmuka dengan nama yang persis sama:
java.util.concurrent.Future
java.util.bersamaan.Masa Depan
Antarmuka
java.util.concurrent.Future menjelaskan API untuk bekerja dengan tugas-tugas yang hasilnya kami rencanakan untuk diperoleh di masa depan: metode untuk memperoleh hasil, metode untuk memeriksa status. Kami
Future
tertarik dengan implementasinya
java.util.concurrent.FutureTask . Artinya
Task
, inilah yang akan dieksekusi di
Future
. Yang juga menarik dari implementasi ini adalah penerapan dan
Runnable
. Anda dapat menganggap ini semacam adaptor model lama yang bekerja dengan tugas di utas dan model baru (baru dalam artian muncul di Java 1.5). Berikut ini contohnya:
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 dapat dilihat dari contoh, dengan menggunakan metode ini kita memperoleh
get
hasil dari soal
task
.
(!)Penting, bahwa pada saat hasil diperoleh dengan menggunakan metode tersebut,
get
eksekusi menjadi sinkron. Menurut Anda, mekanisme apa yang akan digunakan di sini? Itu benar, tidak ada blok sinkronisasi - oleh karena itu kita akan melihat
WAITING di JVisualVM bukan sebagai
monitor
atau
wait
, tetapi sebagai yang sama
park
(karena mekanismenya digunakan
LockSupport
).
Antarmuka Fungsional
Selanjutnya kita akan membahas tentang kelas-kelas dari Java 1.8, jadi akan berguna untuk membuat pengenalan singkat. Mari kita lihat kode 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);
}
};
Ada banyak kode yang tidak perlu, bukan? Masing-masing kelas yang dideklarasikan menjalankan satu fungsi, tetapi untuk mendeskripsikannya kami menggunakan sekumpulan kode tambahan yang tidak diperlukan. Dan para pengembang Java juga berpikir demikian. Oleh karena itu, mereka memperkenalkan seperangkat "antarmuka fungsional" (
@FunctionalInterface
) dan memutuskan bahwa sekarang Java sendiri akan "memikirkan" segalanya untuk kita, kecuali yang penting:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier
- pemberi. Ia tidak memiliki parameter, tetapi ia mengembalikan sesuatu, yaitu memasoknya.
Consumer
- konsumen. Dibutuhkan sesuatu sebagai masukan (parameter s) dan melakukan sesuatu dengannya, yaitu mengkonsumsi sesuatu. Ada fungsi lain. Dibutuhkan sesuatu sebagai masukan (parameter
s
), melakukan sesuatu dan mengembalikan sesuatu. Seperti yang bisa kita lihat, obat generik digunakan secara aktif. Jika Anda tidak yakin, Anda dapat mengingatnya dan membaca “
Teori obat generik di Java atau cara mempraktekkan tanda kurung .”
Masa Depan yang Dapat Diselesaikan
Seiring berjalannya waktu, Java 1.8 memperkenalkan kelas baru bernama
CompletableFuture
. Ini mengimplementasikan antarmuka
Future
, artinya antarmuka kita
task
akan dieksekusi di masa depan dan kita dapat mengeksekusi
get
dan mendapatkan hasilnya. Namun dia juga menerapkan beberapa
CompletionStage
. Dari terjemahannya tujuannya sudah jelas: ini adalah Tahapan tertentu dari semacam perhitungan. Pengantar singkat tentang topik ini dapat ditemukan di ikhtisar "
Pengantar Tahap Penyelesaian dan Masa Depan yang Dapat Diselesaikan ". Mari kita langsung ke intinya. Mari kita lihat daftar metode statis yang tersedia untuk membantu kita memulai:
Berikut adalah opsi 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 kode ini, kita akan melihat bahwa pembuatan
CompletableFuture
melibatkan permulaan seluruh rantai. Oleh karena itu, meskipun ada beberapa kesamaan dengan SteamAPI dari Java8, inilah perbedaan antara pendekatan-pendekatan ini. Misalnya:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
System.out.println("Executed");
return value.toUpperCase();
});
Ini adalah contoh Java 8 Stream Api (Anda dapat membacanya lebih lanjut di sini "
Panduan Java 8 Stream API dalam Gambar dan Contoh "). Jika Anda menjalankan kode ini, kode ini
Executed
tidak akan ditampilkan. Artinya, saat membuat aliran di Java, aliran tersebut tidak langsung dimulai, tetapi menunggu hingga diperlukan nilai darinya. Namun
CompletableFuture
rantai tersebut segera dimulai untuk dieksekusi, tanpa menunggu nilai yang dihitung diminta. Saya pikir penting untuk memahami hal ini. Jadi kita memiliki CompletableFuture. Bagaimana kita dapat menciptakan sebuah rantai dan sarana apa yang kita miliki? Mari kita ingat tentang antarmuka fungsional yang kita tulis sebelumnya.
- Kami memiliki fungsi (
Function
) yang mengambil A dan mengembalikan B. Ia memiliki satu metode - apply
(berlaku).
- Kami memiliki konsumen (
Consumer
) yang menerima A dan tidak mengembalikan apa pun ( Void ). Ia hanya memiliki satu metode - accept
(terima).
- Kami memiliki kode yang berjalan di thread
Runnable
yang tidak menerima atau mengembalikan. Ia memiliki satu metode - run
(jalankan).
Hal kedua yang perlu diingat adalah
CompletalbeFuture
dalam pekerjaannya menggunakan
Runnable
konsumen dan fungsi. Mengingat hal ini, Anda selalu ingat bahwa Anda
CompletableFuture
dapat 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);
}
Metode
thenRun
memiliki
thenApply
versi .
thenAccept
_
Async
Artinya tahapan ini akan dieksekusi di thread baru. Nanti diambil dari kolam khusus, jadi belum diketahui terlebih dahulu alirannya seperti apa, baru atau lama. Itu semua tergantung pada seberapa sulit tugasnya. Selain metode tersebut, ada tiga kemungkinan menarik lainnya. Untuk lebih jelasnya, bayangkan kita memiliki layanan tertentu yang menerima pesan dari suatu tempat dan itu membutuhkan waktu:
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 fitur-fitur lain yang
CompletableFuture
. Kita dapat menggabungkan hasilnya
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 dicatat bahwa secara default threadnya adalah thread daemon, jadi untuk kejelasan
get
, kami biasa menunggu hasilnya. Dan kita tidak hanya dapat menggabungkan (combine), tetapi juga mengembalikan
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
Di sini saya ingin mencatat bahwa untuk singkatnya, metode ini digunakan
CompletableFuture.completedFuture
. Metode ini tidak membuat thread baru, jadi sisa rantai akan dieksekusi di thread yang sama tempat thread tersebut dipanggil
completedFuture
. Ada juga metodenya
thenAcceptBoth
. Ini sangat mirip dengan
accept
, tetapi jika
thenAccept
menerima
consumer
, maka
thenAcceptBoth
ia menerima
CompletableStage
+ lain sebagai input
BiConsumer
, yaitu
consumer
, yang menerima 2 sumber sebagai input, bukan satu. Ada kemungkinan menarik lainnya dengan kata tersebut
Either
:
Metode ini menerima alternatif
CompletableStage
dan akan dieksekusi pada metode
CompletableStage
yang dijalankan terlebih dahulu. Dan saya ingin mengakhiri ulasan ini dengan fitur menarik lainnya
CompletableFuture
- penanganan kesalahan.
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
.thenAccept(val -> System.out.println(val));
Kode ini tidak akan melakukan apa pun, karena... pengecualian akan dilempar dan tidak akan terjadi apa-apa. Namun jika kita menghapus komentar
exceptionally
, maka kita mendefinisikan perilakunya.
CompletableFuture
Saya juga merekomendasikan menonton video berikut tentang topik ini :
Menurut pendapat saya, video-video ini termasuk yang paling visual di Internet. Harus jelas dari mereka bagaimana cara kerjanya, persenjataan apa yang kita miliki dan mengapa semua itu diperlukan.
Kesimpulan
Mudah-mudahan sekarang sudah jelas bagaimana thread dapat digunakan untuk mengambil perhitungan setelah dihitung. Material tambahan:
#Viacheslav
GO TO FULL VERSION