JavaRush /مدونة جافا /Random-AR /لا يمكنك تدمير جافا بخيط: الجزء الرابع - القابل للاستدعاء...
Viacheslav
مستوى

لا يمكنك تدمير جافا بخيط: الجزء الرابع - القابل للاستدعاء والمستقبل والأصدقاء

نشرت في المجموعة

مقدمة

لقد نظرنا بالفعل في كيفية إنشاء سلاسل الرسائل في الجزء الأول . دعونا نتذكر مرة أخرى. لا يمكنك إفساد جافا بموضوع: الجزء الرابع - القابل للاستدعاء والمستقبل والأصدقاء - 1مؤشر الترابط هو Threadشيء يتم تشغيله فيه run، لذلك دعونا نستخدم برنامج التحويل البرمجي الخاص بـ Tutorialspoint Java ونقوم بتنفيذ التعليمات البرمجية التالية:
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!";
};
ولكن ماذا نفعل معها؟ لماذا نحتاج حتى إلى مهمة تعمل على سلسلة رسائل تُرجع نتيجة؟ من الواضح أننا نتوقع في المستقبل الحصول على نتيجة الإجراءات التي سيتم تنفيذها في المستقبل. المستقبل باللغة الإنجليزية - المستقبل. وهناك واجهة بنفس الاسم تمامًا: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 ليس كـ monitorأو wait، ولكن بنفس الطريقة park(نظرًا لاستخدام الآلية LockSupport).

واجهات وظيفية

سنتحدث بعد ذلك عن فئات Java 1.8، لذا سيكون من المفيد تقديم مقدمة مختصرة. دعونا نلقي نظرة على الكود التالي:
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);
	}
};
هناك الكثير من التعليمات البرمجية غير الضرورية، أليس كذلك؟ تؤدي كل فئة من الفئات المعلنة وظيفة واحدة، ولكن لوصفها نستخدم مجموعة من التعليمات البرمجية المساعدة غير الضرورية. وقد اعتقد مطورو 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)، ويفعل شيئًا ويعيد شيئًا ما. كما نرى، يتم استخدام الأدوية الجنيسة بنشاط. إذا لم تكن متأكدًا، يمكنك تذكرها وقراءة " نظرية الأدوية الجنيسة في Java أو كيفية وضع الأقواس موضع التنفيذ ."

المستقبل الكامل

مع مرور الوقت، قدمت Java 1.8 فئة جديدة تسمى CompletableFuture. إنه ينفذ الواجهة Future، مما يعني أنه taskسيتم تنفيذ الواجهة الخاصة بنا في المستقبل ويمكننا التنفيذ getوالحصول على النتيجة. لكنه ينفذ أيضًا بعض ملفات CompletionStage. من الترجمة، غرضها واضح بالفعل: إنها مرحلة معينة من نوع ما من الحساب. يمكن العثور على مقدمة موجزة للموضوع في النظرة العامة " مقدمة إلى مرحلة الإكمال والمستقبل الكامل ". دعنا نصل مباشرة إلى هذه النقطة. دعونا نلقي نظرة على قائمة الطرق الثابتة المتاحة لمساعدتنا على البدء: لا يمكنك إفساد جافا بموضوع: الجزء الرابع - القابل للاستدعاء والمستقبل والأصدقاء - 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يتضمن بدء السلسلة بأكملها. لذلك، على الرغم من وجود بعض التشابه مع SteamAPI من Java8، فإن هذا هو الفرق بين هذه الأساليب. على سبيل المثال:
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 بالصور والأمثلة "). إذا قمت بتشغيل هذا الرمز، فلن Executedيتم عرضه. أي أنه عند إنشاء دفق في Java، لا يبدأ الدفق على الفور، ولكنه ينتظر حتى تكون القيمة مطلوبة منه. ولكنها CompletableFutureتبدأ السلسلة للتنفيذ فورًا، دون انتظار أن يُطلب منها القيمة المحسوبة. أعتقد أنه من المهم أن نفهم هذا. لذلك لدينا مستقبل كامل. كيف يمكننا إنشاء سلسلة وما هي الوسائل المتوفرة لدينا؟ دعونا نتذكر الواجهات الوظيفية التي كتبنا عنها سابقًا.
  • لدينا دالة ( Function) تأخذ A وترجع B. ولها طريقة واحدة - apply(apply).
  • لدينا مستهلك ( Consumer) يقبل A ولا يُرجع شيئًا (Void). لها طريقة واحدة فقط - accept(القبول).
  • لدينا كود يعمل على موضوع Runnableلا يقبل أو يعود. لها طريقة واحدة - 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لها 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، نستخدم انتظار النتيجة. ولا يمكننا فقط الجمع بين (combine) ولكن أيضًا إرجاع 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الذي يقبل مصدرين كمدخل، وليس مصدرًا واحدًا. هناك احتمال آخر مثير للاهتمام فيما يتعلق بالكلمة Either: لا يمكنك تدمير جافا بخيط: الجزء الرابع - القابل للاستدعاء والمستقبل والأصدقاء - 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));
هذا الكود لن يفعل أي شيء، لأن... سيتم طرح استثناء ولن يحدث شيء. ولكن إذا قمنا بإلغاء التعليق exceptionally، فإننا نحدد السلوك. CompletableFutureوأوصي أيضًا بمشاهدة الفيديو التالي حول هذا الموضوع : في رأيي المتواضع، تعد مقاطع الفيديو هذه من أكثر مقاطع الفيديو المرئية على الإنترنت. يجب أن يكون واضحا منهم كيف يعمل كل شيء، وما هي الترسانة التي لدينا ولماذا نحن بحاجة إليها.

خاتمة

نأمل أن يكون من الواضح الآن كيف يمكن استخدام سلاسل الرسائل لاسترداد الحسابات بعد حسابها. مواد اضافية: # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION