JavaRush /Java блогы /Random-KK /Сіз Java-ны жіппен бұза алмайсыз: IV бөлім - Шақырылатын,...
Viacheslav
Деңгей

Сіз Java-ны жіппен бұза алмайсыз: IV бөлім - Шақырылатын, болашақ және достар

Топта жарияланған

Кіріспе

Біз бірінші бөлімде ағындардың қалай жасалатынын қарастырдық . Тағы да еске түсірейік. Сіз Java тілін жіппен бүлдіре алмайсыз: IV бөлім - Шақыруға болатын, болашақ және достар - 1Жіп - Threadонда жұмыс істейтін нәрсе , сондықтан java онлайн компиляторынrun қолданып , келесі codeты орындаймыз:
public class HelloWorld {

    public static void main(String []args){
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
Бұл ағындағы тапсырманы орындаудың жалғыз мүмкіндігі ме?

java.util.concurrent.Callable

Java.lang.Runnable файлының ағасы бар және оның аты java.util.concurrent.Callable және ол Java 1.5-те дүниеге келген. Қандай айырмашылықтар бар? Осы интерфейстің JavaDoc нұсқасын мұқият қарастырсақ, жаңа интерфейстен айырмашылығы , нәтижені қайтаратын Runnableәдісті жариялайтынын көреміз. callСондай-ақ, әдепкі бойынша ол Ерекше жағдайды шығарады. try-catchЯғни, ол бізді тексерілген ерекшеліктер үшін блоктарды жазу қажеттілігінен құтқарады . Қазірдің өзінде жаман емес, солай ма? RunnableЕнді оның орнына жаңа тапсырма бар :
Callable task = () -> {
	return "Hello, World!";
};
Бірақ онымен не істеу керек? Неліктен бізге нәтижені қайтаратын ағында орындалатын тапсырма қажет? Алдағы уақытта атқарылатын іс-әрекеттердің нәтижесін алдағы уақытта аламыз деп күтетініміз анық. Ағылшын тілінде Future - Future. Дәл осындай атаумен интерфейс бар:java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Future интерфейсі болашақта нәтижелерін алуды жоспарлаған тапсырмалармен жұмыс істеуге арналған API интерфейсін сипаттайды: нәтижелерді алу әдістері, күйді тексеру әдістері. Біз оны java.util.concurrent.FutureTaskFuture іске асыруға мүдделіміз . Яғни , бұл орындалатын болады . Бұл іске асырудың қызығы, ол іске асырады және . Сіз бұл ағындардағы тапсырмалармен жұмыс істеудің ескі үлгісінің және жаңа үлгінің адаптерінің бір түрін қарастыра аласыз (жаңа, ол java 1.5-те пайда болды деген мағынада). Міне, мысал: TaskFutureRunnable
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());
    }
}
Мысалдан көрініп тұрғандай, әдісті қолданып getесептің нәтижесін аламыз task. (!)Маңызды, әдіс арқылы нәтиже алынған сәтте getорындалу синхронды болады. Бұл жерде қандай механизм қолданылады деп ойлайсыз? Дұрыс, синхрондау блогы жоқ, сондықтан біз JVisualVM жүйесінде WAITING функциясынmonitor немесе ретінде емес wait, дәл сол сияқты көреміз park(себебі механизм пайдаланылады LockSupport).

Функционалдық интерфейстер

Әрі қарай Java 1.8 сыныптары туралы айтатын боламыз, сондықтан қысқаша кіріспе жасау пайдалы болар еді. Келесі 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);
	}
};
Керек емес code көп, солай емес пе? Жарияланған сыныптардың әрқайсысы бір функцияны орындайды, бірақ оны сипаттау үшін біз қажет емес көмекші codeтардың жиынтығын қолданамыз. Java әзірлеушілері де солай ойлады. Сондықтан олар «функционалды интерфейстер» ( ) жинағын енгізді @FunctionalInterfaceжәне қазір Java өзі біз үшін маңыздылардан басқа бәрін «ойланады» деп шешті:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- провайдер. Оның параметрлері жоқ, бірақ ол бір нәрсені қайтарады, яғни оны қамтамасыз етеді. Consumer- тұтынушы. Ол кіріс (параметр s) ретінде бір нәрсені қабылдайды және онымен бірдеңе жасайды, яғни бір нәрсені тұтынады. Басқа функция бар. Ол кіріс (параметр) ретінде бірдеңені қабылдайды s, бірдеңе жасайды және бір нәрсені қайтарады. Көріп отырғанымыздай, генериктер белсенді түрде қолданылады. Егер сенімді болмасаңыз, оларды есте сақтап, « Java тіліндегі генериктердің теориясы немесе жақшаларды іс жүзінде қалай қою керек » бөлімін оқуға болады.

CompletableFuture

Уақыт өте келе Java 1.8 жаңа сыныпты ұсынды CompletableFuture. Ол интерфейсті жүзеге асырады Future, яғни біздікі taskболашақта орындалады және біз орындап get, нәтиже аламыз. Бірақ ол кейбіреулерін де жүзеге асырады CompletionStage. Аудармадан оның мақсаты анық көрінеді: бұл қандай да бір есептеудің белгілі бір кезеңі. Тақырыпқа қысқаша кіріспе « ComletionStage және CompletableFuture бағдарламасына кіріспе » шолуынан табуға болады. Тікелей мәселеге көшейік. Жұмысты бастауға көмектесетін қолжетімді статикалық әдістердің тізімін қарастырайық: Сіз Java-ны жіппен бүлдіре алмайсыз: IV бөлім - Шақыруға болатын, болашақ және достар - 2Міне, оларды пайдалану опциялары:
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 "Значение";
        });
    }
}
CompletableFutureБұл codeты іске қоссақ, құру бүкіл тізбекті бастауды қамтитынын көреміз . Сондықтан, Java8-тен SteamAPI-мен кейбір ұқсастықтар болғанымен, бұл әдістер арасындағы айырмашылық. Мысалы:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
Бұл Java 8 Stream Api мысалы (бұл туралы толығырақ мына жерден оқи аласыз " Java 8 Stream API нұсқаулығы суреттер мен мысалдардағы "). Бұл codeты іске қоссаңыз, ол Executedкөрсетілмейді. Яғни, Java-да ағын жасау кезінде ағын бірден басталмайды, бірақ одан мән қажет болғанша күтеді. Бірақ CompletableFutureол есептелген мәннің сұралуын күтпей-ақ бірден орындау үшін тізбекті бастайды. Мұны түсіну маңызды деп ойлаймын. Сонымен, бізде CompletableFuture бар. Біз тізбекті қалай жасай аламыз және бізде қандай құралдар бар? Біз бұрын жазған функционалды интерфейстер туралы еске түсірейік.
  • FunctionБізде A қабылдайтын және B қайтаратын ( ) функциясы бар. Оның жалғыз әдісі бар - apply(қолдану).
  • ConsumerБізде A қабылдайтын және ештеңені қайтармайтын тұтынушы ( ) бар ( Void ). Оның бір ғана әдісі бар - accept(қабылдау).
  • RunnableБізде қабылдамайтын немесе қайтарылмайтын ағында жұмыс істейтін code бар . Оның жалғыз әдісі бар - run(іске қосу).
Есте сақтау керек екінші нәрсе, CompletalbeFutureол өз жұмысында Runnableтұтынушылар мен функцияларды пайдаланады. Осыны ескере отырып, сіз 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);
}
Әдістердің нұсқалары thenRunбар . _ Бұл бұл кезеңдердің жаңа ағында орындалатынын білдіреді. Ол арнайы бассейннен алынады, сондықтан оның жаңа немесе ескі ағынының қандай болатыны алдын ала белгісіз. Мұның бәрі тапсырмалардың қаншалықты қиын екеніне байланысты. Бұл әдістерге қосымша тағы үш қызықты мүмкіндік бар. Түсінікті болу үшін бізде бір жерден хабарлама алатын және уақытты қажет ететін белгілі бір қызмет бар деп елестетейік: thenApplythenAcceptAsync
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
Енді басқа мүмкіндіктерді қарастырайық CompletableFuture. CompletableFutureНәтижені басқа нәтижемен біріктіре аламыз 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();
Айта кету керек, әдепкі бойынша ағындар демон ағындары болады, сондықтан түсінікті болу үшін getбіз нәтижені күту үшін қолданамыз. Біз біріктіріп қана қоймай (біріктіре аламыз), сонымен қатар қайтара аламыз CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
Мұнда қысқаша айтқанда, әдіс қолданылғанын атап өткім келеді CompletableFuture.completedFuture. Бұл әдіс жаңа ағынды жасамайды, сондықтан тізбектің қалған бөлігі ол шақырылған ағында орындалады completedFuture. Әдісі де бар thenAcceptBoth. Ол -ға өте ұқсас accept, бірақ thenAcceptқабылдаса consumer, онда кіріс ретінде басқа + thenAcceptBothқабылдайды , яғни бір емес, 2 көзді кіріс ретінде қабылдайды. Сөзімен тағы бір қызықты мүмкіндік бар : Бұл әдістер баламаны қабылдайды және бірінші орындалған әдіс бойынша орындалады . Мен бұл шолуды тағы бір қызықты мүмкіндікпен аяқтағым келеді - қателерді өңдеу. CompletableStageBiConsumerconsumerEitherСіз Java-ны жіппен бұза алмайсыз: IV бөлім - Шақыруға болатын, болашақ және достар - 3CompletableStageCompletableStageCompletableFuture
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
Бұл code ештеңе жасамайды, себебі... ерекшелік тасталады және ештеңе болмайды. Бірақ егер біз түсініктеме бермесек exceptionally, онда мінез-құлықты анықтаймыз. Мен сондай-ақ осы тақырып бойынша CompletableFutureкелесі бейнені қарауды ұсынамын : Менің қарапайым пікірімше, бұл бейнелер Интернеттегі ең көрнекі бейнелердің бірі. Олардан мұның бәрі қалай жұмыс істейтіні, бізде қандай арсенал бар және мұның бәрі не үшін қажет екені анық болуы керек.

Қорытынды

Есептеулер есептелгеннен кейін ағындарды алу үшін оларды қалай пайдалануға болатыны енді анық болды деп үміттенеміз. Қосымша материал: #Вячеслав
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION