JavaRush /Java Blog /Random-TL /Hindi mo masisira ang Java gamit ang isang thread: Part I...

Hindi mo masisira ang Java gamit ang isang thread: Part IV - Callable, Future at mga kaibigan

Nai-publish sa grupo

Panimula

Napagmasdan na natin kung paano nilikha ang mga thread sa unang bahagi . Alalahanin natin muli. Hindi mo masisira ang Java gamit ang isang thread: Part IV - Callable, Future at mga kaibigan - 1Ang isang thread ay Threadisang bagay na tumatakbo dito run, kaya gamitin natin ang tutorialspoint java online 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 pagpapatakbo ng isang gawain sa isang thread?

java.util.concurrent.Callable

May kapatid pala si java.lang.Runnable at ang pangalan niya ay java.util.concurrent.Callable at ipinanganak siya sa Java 1.5. Ano ang mga pagkakaiba? Kung susuriin nating mabuti ang JavaDoc ng interface na ito, makikita natin na, hindi katulad ng Runnable, ang bagong interface ay nagdedeklara ng paraan callna nagbabalik ng resulta. Gayundin, sa pamamagitan ng default, ito ay nagtatapon ng Exception. Ibig sabihin, inililigtas tayo nito mula sa pangangailangang magsulat try-catchng mga bloke para sa mga nasuri na eksepsiyon. Hindi na masama, tama ba? Ngayon ay mayroon na tayong Runnablebagong gawain:
Callable task = () -> {
	return "Hello, World!";
};
Ngunit ano ang gagawin dito? Bakit kailangan pa natin ng gawain na tumatakbo sa isang thread na nagbabalik ng resulta? Malinaw, sa hinaharap inaasahan naming matatanggap ang resulta ng mga aksyon na isasagawa sa hinaharap. Kinabukasan sa English - Hinaharap. At mayroong isang interface na may eksaktong parehong pangalan:java.util.concurrent.Future

java.util.concurrent.Future

Ang java.util.concurrent.Future interface ay naglalarawan ng isang API para sa pagtatrabaho sa mga gawain na ang mga resulta ay pinaplano naming makuha sa hinaharap: mga pamamaraan para sa pagkuha ng mga resulta, mga pamamaraan para sa pagsuri sa katayuan. FutureInteresado kami sa pagpapatupad nito java.util.concurrent.FutureTask . Ibig sabihin Task, ito ang isasagawa sa Future. Ang nakakainteres din sa pagpapatupad na ito ay ang pagpapatupad nito at Runnable. Maaari mong isaalang-alang ito bilang isang uri ng adaptor 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 mula sa halimbawa, gamit ang pamamaraan na nakukuha natin getang resulta mula sa problema task. (!)Mahalaga, na sa sandaling ang resulta ay nakuha gamit ang pamamaraan, getang pagpapatupad ay nagiging kasabay. Anong mekanismo sa tingin mo ang gagamitin dito? Iyan ay tama, walang synchronization block - samakatuwid ay makikita natin ang PAGHINTAY sa JVisualVM hindi bilang monitoro wait, ngunit bilang ang mismong isa park(dahil ang mekanismo ay ginagamit LockSupport).

Mga Functional na Interface

Susunod na pag-uusapan natin ang tungkol sa mga klase mula sa Java 1.8, kaya magiging kapaki-pakinabang na gumawa ng maikling pagpapakilala. Tingnan natin 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);
	}
};
Mayroong maraming hindi kinakailangang code, hindi ba? Ang bawat isa sa mga ipinahayag na klase ay gumaganap ng isang solong function, ngunit upang ilarawan ito ay gumagamit kami ng isang bungkos ng hindi kinakailangang auxiliary code. At naisip din ng mga developer ng Java. Samakatuwid, ipinakilala nila ang isang hanay ng mga "functional interface" ( @FunctionalInterface) at nagpasya na ngayon ang Java mismo ay "iisipan" ang lahat para sa amin, maliban sa mga mahalaga:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- provider. Ito ay walang mga parameter, ngunit ito ay nagbabalik ng isang bagay, iyon ay, ito ay nagbibigay nito. Consumer- mamimili. Ito ay tumatagal ng isang bagay bilang input (parameter s) at may ginagawa dito, iyon ay, kumonsumo ng isang bagay. May isa pang function. Ito ay tumatagal ng isang bagay bilang input (parameter s), gumagawa ng isang bagay at nagbabalik ng isang bagay. Tulad ng nakikita natin, ang mga generic ay aktibong ginagamit. Kung hindi ka sigurado, maaari mong tandaan ang mga ito at basahin ang " Ang teorya ng generics sa Java o kung paano isagawa ang mga panaklong ."

CompletableFuture

Sa paglipas ng panahon, ipinakilala ng Java 1.8 ang isang bagong klase na tinatawag na CompletableFuture. Ipinapatupad nito ang interface Future, ibig sabihin, ang sa amin taskay isasagawa sa hinaharap at maaari naming isagawa getat makuha ang resulta. Ngunit ipinapatupad din niya ang ilan CompletionStage. Mula sa pagsasalin ay malinaw na ang layunin nito: ito ay isang tiyak na Yugto ng ilang uri ng pagkalkula. Ang isang maikling pagpapakilala sa paksa ay matatagpuan sa pangkalahatang-ideya na " Introduction to CompletionStage and CompletableFuture ". Diretso tayo sa punto. Tingnan natin ang listahan ng mga available na static na pamamaraan upang matulungan tayong makapagsimula: Hindi mo masisira ang Java gamit ang isang thread: Part IV - Callable, Future 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 {
        // 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 "Значение";
        });
    }
}
Kung patakbuhin natin ang code na ito, makikita natin na ang paglikha CompletableFutureay kinabibilangan ng pagsisimula ng buong chain. Samakatuwid, habang mayroong ilang pagkakatulad sa SteamAPI mula sa Java8, ito 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 Java 8 Stream Api (maaari mong basahin ang higit pa tungkol dito " Gabay sa Java 8 Stream API sa Mga Larawan at Mga Halimbawa "). Kung patakbuhin mo ang code na ito, Executedhindi ito ipapakita. Iyon ay, kapag lumilikha ng isang stream sa Java, ang stream ay hindi magsisimula kaagad, ngunit naghihintay hanggang sa isang halaga ay kailangan mula dito. Ngunit CompletableFuturesinisimulan nito ang kadena para sa pagpapatupad kaagad, nang hindi naghihintay na hilingin ito para sa kinakalkula na halaga. Sa tingin ko, mahalagang maunawaan ito. Kaya mayroon kaming CompletableFuture. Paano tayo makakalikha ng isang kadena at anong paraan ang mayroon tayo? Tandaan natin ang tungkol sa mga functional na interface na isinulat natin kanina.
  • Mayroon kaming isang function ( Function) na tumatagal ng A at nagbabalik ng B. Ito ay may isang paraan - apply(ilapat).
  • Mayroon kaming consumer ( Consumer) na tumatanggap ng A at walang ibinabalik ( Void ). Mayroon lamang itong isang paraan - accept(tanggapin).
  • Mayroon kaming code na tumatakbo sa isang thread Runnablena hindi tumatanggap o nagbabalik. Ito ay may iisang paraan - run(run).
Ang pangalawang bagay na dapat tandaan ay na CompletalbeFuturesa trabaho nito ay gumagamit ito Runnableng mga consumer at function. Dahil dito, maaari mong laging tandaan na CompletableFuturemagagawa mo ito:
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 mga pamamaraan thenRunay may thenApplymga bersyon . thenAccept_ AsyncNangangahulugan ito na ang mga yugtong ito ay isasagawa sa isang bagong thread. Kukunin ito sa isang espesyal na pool, kaya hindi alam nang maaga kung anong uri ng daloy ito, bago o luma. Ang lahat ay nakasalalay sa kung gaano kahirap 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 mensahe mula sa isang lugar 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 tampok na CompletableFuture. Maaari naming pagsamahin ang resulta 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();
Kapansin-pansin na bilang default ang mga thread ay magiging mga daemon thread, kaya para sa kalinawan get, ginagamit namin upang maghintay para sa resulta. At hindi lamang natin maaaring pagsamahin (pagsamahin), ngunit ibalik din CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
Dito nais kong tandaan na para sa kaiklian, ginamit ang pamamaraan CompletableFuture.completedFuture. Ang pamamaraang ito ay hindi gumagawa ng bagong thread, kaya ang natitirang bahagi ng chain ay isasagawa sa parehong thread kung saan ito tinawag na completedFuture. Mayroon ding pamamaraan thenAcceptBoth. Ito ay halos kapareho sa accept, ngunit kung thenAcceptito ay tumatanggap consumer, pagkatapos ay thenAcceptBothtumatanggap ito ng isa pang CompletableStage+ bilang input BiConsumer, iyon ay consumer, na tumatanggap ng 2 pinagmumulan bilang input, hindi isa. May isa pang kawili-wiling posibilidad sa salitang Either: Hindi mo masisira ang Java gamit ang isang thread: Part IV - Callable, Future at mga kaibigan - 3Ang mga pamamaraang ito ay tumatanggap ng alternatibo CompletableStageat isasagawa sa isa CompletableStagena unang naisakatuparan. At gusto kong tapusin ang pagsusuri na ito gamit ang isa pang kawili-wiling feature 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... isang exception ang itatapon at walang mangyayari. Ngunit kung aalisin natin ang komento exceptionally, tutukuyin natin ang pag-uugali. CompletableFutureInirerekomenda ko rin na panoorin ang sumusunod na video sa paksang ito : Sa aking mapagpakumbabang opinyon, ang mga video na ito ay ilan sa mga pinaka-visual sa Internet. Dapat itong maging malinaw sa kanila kung paano gumagana ang lahat, kung anong arsenal ang mayroon tayo at kung bakit kailangan ang lahat ng ito.

Konklusyon

Sana ay malinaw na ngayon kung paano magagamit ang mga thread upang kunin ang mga kalkulasyon pagkatapos na makalkula ang mga ito. Karagdagang materyal: #Viacheslav
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION