JavaRush /Java Blog /Random-ID /Anda tidak dapat merusak Java dengan utas: Bagian IV - Ca...
Viacheslav
Level 3

Anda tidak dapat merusak Java dengan utas: Bagian IV - Callable, Future, dan teman-teman

Dipublikasikan di grup Random-ID

Perkenalan

Kita telah melihat bagaimana thread dibuat di bagian pertama . Mari kita ingat lagi. Anda tidak dapat merusak Java dengan utas: Bagian IV - Callable, Future, dan teman-teman - 1Thread adalah Threadsesuatu 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 callyang mengembalikan hasil. Juga, secara default ia memunculkan Pengecualian. Artinya, ini menyelamatkan kita dari kebutuhan untuk menulis try-catchblok untuk pengecualian yang dicentang. Sudah lumayan, kan? Sekarang kami memiliki Runnabletugas 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 Futuretertarik 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 gethasil dari soal task. (!)Penting, bahwa pada saat hasil diperoleh dengan menggunakan metode tersebut, geteksekusi 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 monitoratau 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 taskakan dieksekusi di masa depan dan kita dapat mengeksekusi getdan 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: Anda tidak dapat merusak Java dengan utas: Bagian IV - Callable, Future, dan teman-teman - 2Berikut adalah opsi untuk menggunakannya:
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 "Значение";
        });
    }
}
Jika kita menjalankan kode ini, kita akan melihat bahwa pembuatan CompletableFuturemelibatkan 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 Executedtidak akan ditampilkan. Artinya, saat membuat aliran di Java, aliran tersebut tidak langsung dimulai, tetapi menunggu hingga diperlukan nilai darinya. Namun CompletableFuturerantai 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 Runnableyang tidak menerima atau mengembalikan. Ia memiliki satu metode - run(jalankan).
Hal kedua yang perlu diingat adalah CompletalbeFuturedalam pekerjaannya menggunakan Runnablekonsumen dan fungsi. Mengingat hal ini, Anda selalu ingat bahwa Anda CompletableFuturedapat 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 computation
        CompletableFuture.runAsync(task)
                         .thenApply((v) -> longValue.get())
                         .thenApply(dateConverter)
                         .thenAccept(printer);
}
Metode thenRunmemiliki thenApplyversi . thenAccept_ AsyncArtinya 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 CompletableFuturedengan 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 thenAcceptmenerima consumer, maka thenAcceptBothia menerima CompletableStage+ lain sebagai input BiConsumer, yaitu consumer, yang menerima 2 sumber sebagai input, bukan satu. Ada kemungkinan menarik lainnya dengan kata tersebut Either: Anda tidak dapat merusak Java dengan utas: Bagian IV - Callable, Future, dan teman-teman - 3Metode ini menerima alternatif CompletableStagedan akan dieksekusi pada metode CompletableStageyang 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)
				 //.exceptionally(ex -> 0L)
				 .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. CompletableFutureSaya 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
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION