JavaRush /Java блогу /Random-KY /Javaны жип менен буза албайсыз: IV бөлүк - Чалуу, Келечек...
Viacheslav
Деңгээл

Javaны жип менен буза албайсыз: IV бөлүк - Чалуу, Келечек жана достор

Группада жарыяланган

Киришүү

Биз биринчи бөлүктө жиптер кантип түзүлөөрүн карап чыктык . Дагы бир жолу эстейли. Javaны жип менен буза албайсыз: IV бөлүк - Чакыруу, Келечек жана достор - 1Жип - бул Threadанда иштеген нерсе , андыктан tutorialspoint 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натыйжаны кайтаруучу ыкманы жарыялаганын көрөбүз. Ошондой эле, демейки боюнча, ал Exception ыргытат. try-catchБашкача айтканда, бул бизди текшерилген өзгөчөлүктөр үчүн блокторду жазуу зарылдыгынан куткарат . Жаман эмес, туурабы? RunnableЭми анын ордуна жаңы тапшырмабыз бар :
Callable task = () -> {
	return "Hello, World!";
};
Бирок аны менен эмне кылуу керек? Эмне үчүн бизге натыйжа берген жипте иштеген тапшырма керек? Албетте, келечекте биз келечекте аткарыла турган иш-аракеттердин натыйжасын күтөбүз. Англисче Future - Future. Жана дал ушундай аталыштагы интерфейс бар:java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Future интерфейси келечекте биз жыйынтыктарын алууну пландаштырган тапшырмалар менен иштөө үчүн APIди сүрөттөйт: натыйжаларды алуу ыкмалары, статусту текшерүү ыкмалары. Биз Futureанын аткарылышына кызыкдар java.util.concurrent.FutureTask . Бул эмне Task, эмне болот Future. Бул ишке ашыруунун дагы кызыктуусу, ал ишке ашырат жана Runnable. Сиз муну жиптердеги тапшырмалар менен иштөөнүн эски моделинин жана жаңы моделдин адаптеринин бир түрү деп эсептей аласыз (жаңы, ал java 1.5те пайда болгон деген мааниде). Бул жерде бир мисал:
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де WAITINGmonitor дегенди же катары эмес 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'да агым түзүүдө агым дароо башталbyte, бирок андан маани керек болгонго чейин күтөт. Бирок CompletableFutureал эсептелген нарктын суралышын күтпөстөн, дароо аткаруу үчүн чынжырды баштайт. Мен муну түшүнүү маанилүү деп ойлойм. Ошентип, бизде CompletableFuture бар. Кантип чынжырды түзө алабыз жана бизде кандай каражаттар бар? Биз буга чейин жазган функционалдык интерфейстерди эстейли.
  • Бизде ( ) функциясы бар Function, ал А ны алып, B кайтарат. Анын бир гана ыкмасы бар - apply(колдонуу).
  • Бизде керектөөчүбүз ( Consumer) А кабыл алып, эч нерсе кайтарbyte ( 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);
}
Методдордун versionлары 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ал башка CompletableStage+ киргизүү катары кабыл алат BiConsumer, башкача айтканда consumer, бир эмес, 2 булакты киргизүү катары кабыл алат. Сөз менен дагы бир кызыктуу мүмкүнчүлүк бар Either: Сиз Javaны жип менен буза албайсыз: IV бөлүк - Чалуу, Келечек жана достор - 3Бул ыкмалар альтернативаны кабыл алат жана биринчи аткарылганга CompletableStageаткарылат . CompletableStageЖана мен бул кароону дагы бир кызыктуу өзгөчөлүк менен бүтүргүм келет CompletableFuture- каталарды иштетүү.
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
Бул code эч нерсе кылbyte, анткени... бир өзгөчөлүк ыргытылат жана эч нерсе болбойт. Бирок биз комментарийден баш тартсак exceptionally, анда жүрүм-турумду аныктайбыз. Мен дагы ушул тема боюнча CompletableFutureтөмөнкү видеону көрүүнү сунуштайм : Менин оюмча, бул видеолор интернеттеги эң визуалдык видеолордун бири. Алардан мунун баары кантип иштээри, бизде кандай арсенал бар жана мунун баары эмне үчүн керек экени түшүнүктүү болушу керек.

Корутунду

Эми жиптер эсептелгенден кийин эсептөөлөрдү алуу үчүн кантип колдонсо болору айкын болду деп үмүттөнөбүз. Кошумча материал: #Вячеслав
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION