Кіріспе
Біз бірінші бөлімде ағындардың қалай жасалатынын қарастырдық . Тағы да еске түсірейік.
Жіп -
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-те пайда болды деген мағынада). Міне, мысал:
Task
Future
Runnable
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 бағдарламасына кіріспе » шолуынан табуға болады. Тікелей мәселеге көшейік. Жұмысты бастауға көмектесетін қолжетімді статикалық әдістердің тізімін қарастырайық:
Міне, оларды пайдалану опциялары:
import java.util.concurrent.CompletableFuture;
public class App {
public static void main(String []args) throws Exception {
CompletableFuture<String> completed;
completed = CompletableFuture.completedFuture("Просто meaning");
CompletableFuture<Void> voidCompletableFuture;
voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("run " + Thread.currentThread().getName());
});
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.runAsync(task)
.thenApply((v) -> longValue.get())
.thenApply(dateConverter)
.thenAccept(printer);
}
Әдістердің нұсқалары
thenRun
бар . _ Бұл бұл кезеңдердің жаңа ағында орындалатынын білдіреді. Ол арнайы бассейннен алынады, сондықтан оның жаңа немесе ескі ағынының қандай болатыны алдын ала белгісіз. Мұның бәрі тапсырмалардың қаншалықты қиын екеніне байланысты. Бұл әдістерге қосымша тағы үш қызықты мүмкіндік бар. Түсінікті болу үшін бізде бір жерден хабарлама алатын және уақытты қажет ететін белгілі бір қызмет бар деп елестетейік:
thenApply
thenAccept
Async
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 көзді кіріс ретінде қабылдайды. Сөзімен тағы бір қызықты мүмкіндік бар : Бұл әдістер баламаны қабылдайды және бірінші орындалған әдіс бойынша орындалады . Мен бұл шолуды тағы бір қызықты мүмкіндікпен аяқтағым келеді - қателерді өңдеу.
CompletableStage
BiConsumer
consumer
Either
CompletableStage
CompletableStage
CompletableFuture
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
.thenAccept(val -> System.out.println(val));
Бұл code ештеңе жасамайды, себебі... ерекшелік тасталады және ештеңе болмайды. Бірақ егер біз түсініктеме бермесек
exceptionally
, онда мінез-құлықты анықтаймыз. Мен сондай-ақ осы тақырып бойынша
CompletableFuture
келесі бейнені қарауды ұсынамын :
Менің қарапайым пікірімше, бұл бейнелер Интернеттегі ең көрнекі бейнелердің бірі. Олардан мұның бәрі қалай жұмыс істейтіні, бізде қандай арсенал бар және мұның бәрі не үшін қажет екені анық болуы керек.
Қорытынды
Есептеулер есептелгеннен кейін ағындарды алу үшін оларды қалай пайдалануға болатыны енді анық болды деп үміттенеміз. Қосымша материал:
#Вячеслав
GO TO FULL VERSION