CodeGym/Blog Java/rawak/Lebih baik bersama: Java dan kelas Thread. Bahagian IV — ...
John Squirrels
Tahap
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan

Diterbitkan dalam kumpulan

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 - 1Benang 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-catchmenyekat 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 getkaedah 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 monitoratau wait, tetapi sebagai kaedah biasa park()(kerana LockSupportmekanisme 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 Supplierbekalan. Ia tidak mempunyai parameter, tetapi ia mengembalikan sesuatu. Ini adalah bagaimana ia membekalkan sesuatu. A Consumermemakan. 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 CompletableFuturemuncul di Java 1.8. Ia melaksanakan Futureantara muka, iaitu tugas kami akan diselesaikan pada masa hadapan, dan kami boleh menghubungi get()untuk mendapatkan hasilnya. Tetapi ia juga melaksanakan CompletionStageantara 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 - 2Berikut 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 CompletableFuturejuga 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 CompletableFuturemula 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 Functionyang mengambil A dan mengembalikan B. Ia mempunyai satu kaedah: apply().
  • Kami mempunyai Consumeryang 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 CompletableFuturemenggunakan Runnable, Consumers, dan Functionsdalam 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 CompletableFuturemenyediakan. Kita boleh menggabungkan hasil a 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();
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 completedFuturedipanggil. Terdapat juga thenAcceptBoth()kaedah. Ia sangat serupa dengan accept(), tetapi jika thenAccept()menerima a Consumer, thenAcceptBoth()menerima satu lagi CompletableStage+ BiConsumersebagai input, iaitu a consumeryang 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 - 3Kaedah ini menerima alternatif CompletableStagedan dilaksanakan pada yang CompletableStagedilaksanakan 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!
Komen
  • Popular
  • Baru
  • Tua
Anda mesti log masuk untuk meninggalkan ulasan
Halaman ini tidak mempunyai sebarang ulasan lagi