Panimula

Sa Bahagi I , sinuri namin kung paano nilikha ang mga thread. Alalahanin natin ng isang beses. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi IV — Matatawagan, Kinabukasan, at mga kaibigan - 1Ang isang thread ay kinakatawan ng klase ng Thread, kung saan run()ang pamamaraan ay tatawagin. Kaya't gamitin natin ang Tutorialspoint online Java compiler at isagawa ang sumusunod na code:

public class HelloWorld {
    
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
Ito ba ang tanging pagpipilian para sa pagsisimula ng isang gawain sa isang thread?

java.util.concurrent.Callable

Lumalabas na ang java.lang.Runnable ay may kapatid na tinatawag na java.util.concurrent.Callable na dumating sa mundo sa Java 1.5. Ano ang mga pagkakaiba? Kung titingnan mong mabuti ang Javadoc para sa interface na ito, makikita namin na, hindi katulad ng Runnable, ang bagong interface ay nagdedeklara ng isang call()paraan na nagbabalik ng resulta. Gayundin, itinapon nito ang Exception bilang default. Ibig sabihin, inililigtas tayo nito mula sa pag- try-catchblock para sa mga nasuri na exception. Hindi masama, tama? Ngayon ay mayroon na tayong bagong gawain sa halip na Runnable:

Callable task = () -> {
	return "Hello, World!";
};
Ngunit ano ang gagawin natin dito? Bakit kailangan natin ng gawain na tumatakbo sa isang thread na nagbabalik ng resulta? Malinaw, para sa anumang mga pagkilos na ginawa sa hinaharap, inaasahan naming matatanggap ang resulta ng mga pagkilos na iyon sa hinaharap. At mayroon kaming interface na may katumbas na pangalan:java.util.concurrent.Future

java.util.concurrent.Future

Ang java.util.concurrent.Future interface ay tumutukoy sa isang API para sa pagtatrabaho sa mga gawain na ang mga resulta ay pinaplano naming matanggap sa hinaharap: mga paraan upang makakuha ng isang resulta, at mga paraan upang suriin ang katayuan. Tungkol sa Future, interesado kami sa pagpapatupad nito sa klase ng java.util.concurrent.FutureTask . Ito ang "Task" na isasagawa sa Future. Ang dahilan kung bakit mas kawili-wili ang pagpapatupad na ito ay nagpapatupad din ito ng Runnable. Maaari mong isaalang-alang ito bilang isang uri ng adaptor sa pagitan ng lumang modelo ng pagtatrabaho sa mga gawain sa mga thread at ang bagong modelo (bago sa kahulugan na ito ay lumitaw sa Java 1.5). Narito ang isang halimbawa:

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());
    }
}
Tulad ng makikita mo mula sa halimbawa, ginagamit namin ang getparaan upang makuha ang resulta mula sa gawain. Tandaan:kapag nakuha mo ang resulta gamit ang get()pamamaraan, ang pagpapatupad ay nagiging kasabay! Anong mekanismo sa tingin mo ang gagamitin dito? Totoo, walang synchronization block. Iyon ang dahilan kung bakit hindi namin makikita ang PAGHIHINTAY sa JVisualVM bilang a monitoro wait, ngunit bilang pamilyar park()na pamamaraan (dahil ang LockSupportmekanismo ay ginagamit).

Mga functional na interface

Susunod, pag-uusapan natin ang tungkol sa mga klase mula sa Java 1.8, kaya makabubuting magbigay tayo ng maikling panimula. Tingnan ang sumusunod na code:

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);
	}
};
Napakaraming dagdag na code, hindi mo ba sasabihin? Ang bawat isa sa mga ipinahayag na klase ay gumaganap ng isang function, ngunit gumagamit kami ng isang grupo ng mga karagdagang sumusuportang code upang tukuyin ito. At ito ay kung paano naisip ng mga developer ng Java. Alinsunod dito, ipinakilala nila ang isang hanay ng mga "functional na interface" ( @FunctionalInterface) at nagpasya na ngayon ang Java mismo ang gagawa ng "pag-iisip", na iniiwan lamang ang mahahalagang bagay na dapat nating alalahanin:

Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Isang Suppliersupply. Wala itong mga parameter, ngunit nagbabalik ito ng isang bagay. Ito ay kung paano ito nagbibigay ng mga bagay. ConsumerKumokonsumo si A. Ito ay tumatagal ng isang bagay bilang isang input (isang argumento) at may ginagawa dito. Ang argumento ay kung ano ang kinakain nito. Tapos meron din kaming Function. Ito ay nangangailangan ng mga input (mga argumento), gumagawa ng isang bagay, at nagbabalik ng isang bagay. Makikita mo na aktibo kaming gumagamit ng mga generic. Kung hindi ka sigurado, maaari kang makakuha ng refresher sa pamamagitan ng pagbabasa ng " Generics in Java: how to use angled brackets in practice ".

CompletableFuture

Lumipas ang oras at lumitaw ang isang bagong klase na tinawag CompletableFuturesa Java 1.8. Ipinapatupad nito ang Futureinterface, ibig sabihin, ang aming mga gawain ay makukumpleto sa hinaharap, at maaari kaming tumawag get()upang makuha ang resulta. Ngunit ipinapatupad din nito ang CompletionStageinterface. Sinasabi ng pangalan ang lahat: ito ay isang tiyak na yugto ng ilang hanay ng mga kalkulasyon. Ang isang maikling pagpapakilala sa paksa ay matatagpuan sa pagsusuri dito: Panimula sa CompletionStage at CompletableFuture. Dumako tayo sa punto. Tingnan natin ang listahan ng mga available na static na pamamaraan na tutulong sa atin na makapagsimula: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi IV — Matatawagan, Kinabukasan, at mga kaibigan - 2Narito ang mga opsyon para sa paggamit ng mga ito:

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";
        });
    }
}
Kung ipapatupad natin ang code na ito, makikita natin na ang paggawa ng isang CompletableFutureay nagsasangkot din ng paglulunsad ng isang buong pipeline. Samakatuwid, na may tiyak na pagkakatulad sa SteamAPI mula sa Java8, dito natin makikita ang pagkakaiba sa pagitan ng mga pamamaraang ito. Halimbawa:

List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
Ito ay isang halimbawa ng Stream API ng Java 8. Kung patakbuhin mo ang code na ito, makikita mong hindi ipapakita ang "Isinagawa". Sa madaling salita, kapag ang isang stream ay ginawa sa Java, ang stream ay hindi agad magsisimula. Sa halip, ito ay naghihintay para sa isang tao na gusto ng isang halaga mula dito. Ngunit CompletableFuturesinimulan agad na isagawa ang pipeline, nang hindi naghihintay na may humingi nito ng halaga. Sa tingin ko ito ay mahalaga upang maunawaan. O, meron tayong CompletableFuture. Paano tayo makakagawa ng pipeline (o chain) at anong mga mekanismo ang mayroon tayo? Alalahanin ang mga functional na interface na isinulat namin kanina.
  • Mayroon kaming isang Functionna tumatagal ng isang A at nagbabalik ng isang B. Ito ay may isang solong pamamaraan: apply().
  • Mayroon kaming isang Consumerkumukuha ng A at walang ibinabalik (Void). Ito ay may iisang paraan: accept().
  • Mayroon kaming Runnable, na tumatakbo sa thread, at walang kinukuha at walang ibinabalik. Ito ay may iisang paraan: run().
Ang susunod na dapat tandaan ay ang CompletableFuturepaggamit ng Runnable, Consumers, at Functionssa gawain nito. Alinsunod dito, maaari mong palaging malaman na magagawa mo ang sumusunod sa 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);
}
Ang thenRun(), thenApply(), at thenAccept()mga pamamaraan ay may mga bersyong "Async." Nangangahulugan ito na ang mga yugtong ito ay makukumpleto sa ibang thread. Ang thread na ito ay kukunin mula sa isang espesyal na pool — kaya hindi natin malalaman nang maaga kung ito ay bago o lumang thread. Ang lahat ay depende sa kung gaano computationally-intensive ang mga gawain. Bilang karagdagan sa mga pamamaraang ito, mayroong tatlong higit pang mga kagiliw-giliw na posibilidad. Para sa kalinawan, isipin natin na mayroon tayong partikular na serbisyo na tumatanggap ng ilang uri ng mensahe mula sa kung saan — at nangangailangan ito ng oras:

public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
Ngayon, tingnan natin ang iba pang mga kakayahan na CompletableFuturenagbibigay. Maaari nating pagsamahin ang resulta ng a CompletableFuturesa resulta ng isa pa 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();
Tandaan na ang mga thread ay mga daemon thread bilang default, kaya para sa kalinawan, ginagamit namin get()upang maghintay para sa resulta. Hindi lamang tayo maaaring pagsamahin CompletableFutures, maaari rin tayong magbalik ng isang CompletableFuture:

CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
Dito nais kong tandaan na ang CompletableFuture.completedFuture()pamamaraan ay ginamit para sa kaiklian. Ang pamamaraang ito ay hindi gumagawa ng bagong thread, kaya ang natitirang pipeline ay isasagawa sa parehong thread kung saan completedFuturetinawag. Mayroon ding thenAcceptBoth()pamamaraan. Ito ay halos kapareho sa accept(), ngunit kung thenAccept()tumatanggap ng Consumer, thenAcceptBoth()tumatanggap ng isa pang CompletableStage+ BiConsumerbilang input, ibig sabihin, a consumerna kumukuha ng 2 pinagmumulan sa halip na isa. May isa pang kawili-wiling kakayahan na inaalok ng mga pamamaraan na ang pangalan ay kinabibilangan ng salitang "Alinman": Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi IV — Matatawagan, Kinabukasan, at mga kaibigan - 3Ang mga pamamaraang ito ay tumatanggap ng alternatibo CompletableStageat isinasagawa sa kung CompletableStagesaan ay unang isasagawa. Sa wakas, gusto kong tapusin ang pagsusuri na ito sa isa pang kawili-wiling tampok ng CompletableFuture: paghawak ng error.

CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
Walang gagawin ang code na ito, dahil magkakaroon ng exception at walang ibang mangyayari. Ngunit sa pamamagitan ng pag-uncomment sa "katangi-tangi" na pahayag, tinutukoy namin ang inaasahang pag-uugali. Speaking of CompletableFuture, inirerekomenda ko rin na panoorin mo ang sumusunod na video: Sa aking mapagpakumbabang opinyon, ito ay kabilang sa mga pinakanagpapaliwanag na mga video sa Internet. Dapat nilang linawin kung paano gumagana ang lahat ng ito, anong toolkit ang mayroon kami, at kung bakit kailangan ang lahat ng ito.

Konklusyon

Sana, malinaw na ngayon kung paano mo magagamit ang mga thread para makakuha ng mga kalkulasyon pagkatapos makumpleto ang mga ito. Karagdagang materyal: Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi I — Mga Thread ng execution Mas mahusay na magkasama: Java at ang Thread class. Bahagi II — Pag-synchronize Mas mahusay na magkasama: Java at ang Thread na klase. Part III — Mas Mahusay na Pakikipag-ugnayan: Java at ang Thread class. Part V — Executor, ThreadPool, Fork/Join Better together: Java at ang Thread class. Bahagi VI - Sunog!