Giriş

Biz artıq birinci hissədə iplərin necə yaradıldığına baxdıq . Bir daha xatırlayaq. Java-nı iplə korlaya bilməzsiniz: IV hissə - Zəng edilə bilən, Gələcək və dostlar - 1Mövzu Threadonun içində işləyən bir şeydir , ona görə də tutorialspoint java onlayn tərtibçisindənrun istifadə edək və aşağıdakı kodu yerinə yetirək:
public class HelloWorld {

    public static void main(String []args){
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
Bu, mövzuda tapşırığı yerinə yetirmək üçün yeganə seçimdir?

java.util.concurrent.Callable

Məlum olub ki, java.lang.Runnable-ın bir qardaşı var və onun adı java.util.concurrent.Callable- dir və Java 1.5-də anadan olub. Fərqlər nələrdir? Bu interfeysin JavaDoc-a daha yaxından nəzər salsaq görərik ki, -dən fərqli olaraq Runnableyeni interfeys callnəticə qaytaran metodu elan edir. Həmçinin, standart olaraq İstisna atır. try-catchYəni bizi yoxlanılan istisnalar üçün bloklar yazmaq ehtiyacından xilas edir . Onsuz da pis deyil, elə deyilmi? Runnableİndi əvəzinə yeni bir vəzifəmiz var :
Callable task = () -> {
	return "Hello, World!";
};
Amma bununla nə etməli? Nəticəni qaytaran bir ip üzərində işləyən bir tapşırığa niyə ehtiyacımız var? Aydındır ki, biz gələcəkdə həyata keçiriləcək tədbirlərin nəticəsini gələcəkdə alacağımızı gözləyirik. İngilis dilində Future - Future. Və eyni adlı bir interfeys var:java.util.concurrent.Future

java.util.concurrent.Future

Java.util.concurrent.Future interfeysi nəticələrini gələcəkdə əldə etməyi planlaşdırdığımız tapşırıqlarla işləmək üçün API-ni təsvir edir: nəticələrin əldə edilməsi üsulları, statusun yoxlanılması üsulları. Biz Futureonun həyata keçirilməsində maraqlıyıq java.util.concurrent.FutureTask . Yəni Taskedam ediləcək şey budur Future. Bu həyata keçirilməsi ilə bağlı maraqlı olan da odur ki, həyata keçirir və Runnable. Bunu iplərdəki tapşırıqlarla işləmək üçün köhnə modelin və yeni modelin bir növ adapteri hesab edə bilərsiniz (java 1.5-də göründüyü mənada yeni). Budur bir nümunə:
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());
    }
}
Nümunədən göründüyü kimi, metoddan istifadə edərək getproblemdən nəticə alırıq task. (!) Vacibdir, metoddan istifadə edərək nəticə əldə edildiyi anda geticra sinxronlaşır. Sizcə, burada hansı mexanizmdən istifadə olunacaq? Düzdür, sinxronizasiya bloku yoxdur - buna görə də biz JVisualVM-də GÖZLƏMƏnimonitor və ya kimi deyil wait, eynisi kimi görəcəyik park(mexanizm istifadə edildiyi üçün LockSupport).

Funksional interfeyslər

Sonra Java 1.8-dən olan dərslər haqqında danışacağıq, ona görə də qısa bir giriş etmək faydalı olardı. Aşağıdakı koda baxaq:
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);
	}
};
Çoxlu lazımsız kodlar var, elə deyilmi? Elan edilmiş siniflərin hər biri bir funksiyanı yerinə yetirir, lakin onu təsvir etmək üçün bir dəstə lazımsız köməkçi koddan istifadə edirik. Java tərtibatçıları da belə düşünürdülər. Buna görə də, onlar bir sıra "funksional interfeyslər" ( @FunctionalInterface) təqdim etdilər və qərara gəldilər ki, indi Java özü bizim üçün vacib olanlardan başqa hər şeyi "düşünəcək":
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- provayder. Parametrləri yoxdur, amma nəyisə qaytarır, yəni onu təmin edir. Consumer- istehlakçı. Giriş kimi nəyisə götürür (parametr s) və onunla nəsə edir, yəni nəyisə istehlak edir. Başqa bir funksiya var. O, bir şeyi giriş (parametr s) kimi qəbul edir, bir şey edir və nəyisə qaytarır. Gördüyümüz kimi, generiklər fəal şəkildə istifadə olunur. Əgər əmin deyilsinizsə, onları xatırlayıb “ Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması ” bölməsini oxuya bilərsiniz.

Tamamlana bilən Gələcək

Zaman keçdikcə Java 1.8 adlı yeni bir sinif təqdim etdi CompletableFuture. O, interfeysi həyata keçirir Future, yəni bizimkilər taskgələcəkdə icra olunacaq və biz icra edib getnəticə əldə edə bilərik. Amma bəzilərini də həyata keçirir CompletionStage. Tərcümədən onun məqsədi artıq aydındır: bu, bir növ hesablamanın müəyyən bir mərhələsidir. Mövzuya qısa girişi " ComletionStage və CompletableFuture-a Giriş " icmalında tapa bilərsiniz . Gəlin birbaşa mətləbə keçək. Başlamağa kömək etmək üçün mövcud statik metodların siyahısına nəzər salaq: Java-nı iplə korlaya bilməzsiniz: IV hissə - Zəng edilə bilən, Gələcək və dostlar - 2Onlardan istifadə üçün seçimlər bunlardır:
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 "Значение";
        });
    }
}
CompletableFutureBu kodu işlətsək, yaradılışın bütün zəncirin başlamasını nəzərdə tutduğunu görərik . Buna görə də, Java8-dən SteamAPI ilə müəyyən oxşarlıq olsa da, bu yanaşmalar arasındakı fərq budur. Misal üçün:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
Bu, Java 8 Stream Api-nin bir nümunəsidir (bu barədə ətraflı burada oxuya bilərsiniz " Şəkillər və Nümunələrdəki Java 8 Stream API Bələdçisi "). Bu kodu işlətsəniz, görünməyəcək Executed. Yəni Java-da axın yaratarkən axın dərhal başlamır, ondan dəyər lazım olana qədər gözləyir. Ancaq CompletableFuturehesablanmış dəyərin soruşulmasını gözləmədən dərhal icra üçün zənciri işə salır. Məncə bunu başa düşmək vacibdir. Beləliklə, bizdə CompletableFuture var. Necə bir zəncir yarada bilərik və hansı vasitələrimiz var? Əvvəllər haqqında yazdığımız funksional interfeysləri xatırlayaq.
  • A qəbul edən və B qaytaran funksiyamız ( Function) var. Onun tək metodu var - apply(tətbiq et).
  • ConsumerBizdə A qəbul edən və heç nə qaytarmayan ( Void ) istehlakçı ( ) var . Onun yalnız bir üsulu var - accept(qəbul edin).
  • RunnableQəbul etməyən və ya geri dönməyən bir mövzuda işləyən kodumuz var . Onun tək bir üsulu var - run(çalışdır).
Xatırlamaq lazım olan ikinci şey, CompletalbeFutureişində Runnableistehlakçılardan və funksiyalardan istifadə etməsidir. Bunu nəzərə alaraq, həmişə bunu edə biləcəyinizi xatırlaya bilərsiniz 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);
}
Metodların versiyaları thenRunvar . _ Bu o deməkdir ki, bu mərhələlər yeni mövzuda icra olunacaq. Xüsusi hovuzdan götürüləcək, ona görə də onun hansı axın olacağı əvvəlcədən bilinmir, yeni və ya köhnə. Hamısı tapşırıqların nə qədər çətin olduğundan asılıdır. Bu üsullara əlavə olaraq, daha üç maraqlı imkan var. Aydınlıq üçün təsəvvür edək ki, haradansa mesaj alan müəyyən bir xidmətimiz var və bunun üçün vaxt lazımdır: thenApplythenAcceptAsync
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
İndi isə gəlin digər xüsusiyyətlərə baxaq CompletableFuture. Nəticəni CompletableFuturebaşqasının nəticəsi ilə birləşdirə bilərik 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();
Qeyd etmək lazımdır ki, standart olaraq mövzular daemon ipləri olacaq, buna görə də aydınlıq üçün getnəticəni gözləmək üçün istifadə edirik. Və biz nəinki birləşdirə bilərik (birləşdirə bilərik), həm də geri qaytara bilərik CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
Burada qeyd etmək istərdim ki, qısalıq üçün metoddan istifadə edilmişdir CompletableFuture.completedFuture. Bu üsul yeni bir ip yaratmır, buna görə də zəncirin qalan hissəsi adlandırıldığı eyni ipdə yerinə yetiriləcəkdir completedFuture. Bir üsul da var thenAcceptBoth. -a çox bənzəyir accept, lakin thenAcceptqəbul edirsə consumer, o zaman giriş kimi başqa bir + thenAcceptBothqəbul edir , yəni bir deyil, 2 mənbəni giriş kimi qəbul edir. Sözlə başqa bir maraqlı ehtimal da var : Bu üsullar alternativi qəbul edir və birinci icra olunanda icra olunacaq . Mən bu araşdırmanı başqa bir maraqlı xüsusiyyətlə - səhvlərin idarə edilməsi ilə bitirmək istərdim .CompletableStageBiConsumerconsumerEitherThread'ом Java не испортишь: Часть IV — Callable, Future и друзья - 3CompletableStageCompletableStageCompletableFuture
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
Bu kod heç nə etməyəcək, çünki... istisna atılacaq və heç nə olmayacaq. Ancaq şərhi ləğv etsək exceptionally, davranışı müəyyənləşdiririk. Bu mövzuda CompletableFutureaşağıdakı videoya baxmağı da tövsiyə edirəm : Mənim təvazökar fikrimcə, bu videolar İnternetdəki ən vizual videolardan biridir. Onlardan hər şeyin necə işlədiyi, hansı arsenalımız olduğu və bunun nə üçün lazım olduğu aydın olmalıdır.

Nəticə

Ümid edirik ki, indi aydındır ki, mövzular hesablandıqdan sonra hesablamaları əldə etmək üçün necə istifadə oluna bilər. Əlavə material: #Viaçeslav