JavaRush /Blog Java /Random-MS /Anda tidak boleh merosakkan Java dengan benang: Bahagian ...

Anda tidak boleh merosakkan Java dengan benang: Bahagian IV - Boleh Dipanggil, Masa Depan dan rakan

Diterbitkan dalam kumpulan

pengenalan

Kami telah melihat bagaimana benang dicipta pada bahagian pertama . Mari kita ingat lagi. Anda tidak boleh merosakkan Java dengan benang: Bahagian IV - Boleh Dipanggil, Masa Depan dan rakan - 1Benang ialah Threadsesuatu 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 callyang mengembalikan hasil. Selain itu, secara lalai ia membuang Exception. Iaitu, ia menyelamatkan kita daripada keperluan untuk menulis try-catchblok untuk pengecualian yang diperiksa. Tak teruk dah kan? Sekarang kita mempunyai Runnabletugas 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 Futureberminat 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 gethasil daripada masalah task. (!)Penting, bahawa pada masa ini keputusan diperoleh menggunakan kaedah, getpelaksanaan 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 monitoratau 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 taskakan dilaksanakan pada masa hadapan dan kami boleh melaksanakan getdan 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: Anda tidak boleh merosakkan Java dengan benang: Bahagian IV - Boleh Dipanggil, Masa Depan dan rakan - 2Berikut ialah pilihan 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 kod ini, kita akan melihat bahawa penciptaan CompletableFuturemelibatkan 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 Executedtidak akan dipaparkan. Iaitu, apabila mencipta aliran di Jawa, aliran tidak bermula serta-merta, tetapi menunggu sehingga nilai diperlukan daripadanya. Tetapi CompletableFutureia 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 Runnableyang tidak menerima atau kembali. Ia mempunyai satu kaedah - run(lari).
Perkara kedua yang perlu diingat ialah CompletalbeFuturedalam kerjanya ia menggunakan Runnablepengguna dan fungsi. Memandangkan ini, anda sentiasa boleh ingat bahawa anda CompletableFutureboleh 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);
}
Kaedah thenRunmempunyai thenApplyversi . thenAccept_ AsyncIni 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 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 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 thenAcceptia menerima consumer, maka thenAcceptBothia 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: Anda tidak boleh merosakkan Java dengan benang: Bahagian IV - Boleh Dipanggil, Masa Depan dan rakan - 3Kaedah ini menerima alternatif CompletableStagedan akan dilaksanakan pada kaedah CompletableStageyang 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)
				 //.exceptionally(ex -> 0L)
				 .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. CompletableFutureSaya 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
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION