Java 8 มาแล้ว ถึงเวลาเรียนรู้สิ่งใหม่ๆ Java 7 และ Java 6 เป็นเพียงเวอร์ชันที่ได้รับการปรับเปลี่ยนเล็กน้อย แต่ Java 8 จะมีการปรับปรุงที่สำคัญ บางที Java 8 อาจใหญ่เกินไป? วันนี้ฉันจะให้คำอธิบายโดยละเอียดเกี่ยวกับ Abstraction CompletableFuture ใหม่ใน JDK 8 ดังที่เราทุกคนทราบกันดีว่า Java 8 จะเปิดตัวภายในเวลาไม่ถึงหนึ่งปี ดังนั้นบทความนี้จึงอิงจาก JDK 8 build 88 ที่รองรับ lambda CompletableFuture ขยาย Future จัดเตรียมวิธีการ ตัวดำเนินการแบบเอกเทศ และส่งเสริมความไม่ตรงกันและโมเดลการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์ซึ่งไม่ได้หยุดอยู่แค่ Java เวอร์ชันเก่าเท่านั้น หากคุณเปิด JavaDoc ของ CompletableFuture คุณจะตกใจ มีประมาณห้าสิบวิธี (!) และบางวิธีก็น่าสนใจและเข้าใจยากเช่น:
คัดลอกรหัสดังต่อไปนี้: สาธารณะ <U,V> CompletableFuture<V> แล้วCombineAsync(
CompletableFuture<? ขยาย U> อื่น ๆ
BiFunction<? super T,? super U,? ขยาย V> fn,
ผู้ดำเนินการ ผู้ดำเนินการ)
ไม่ต้องกังวลอ่านต่อ CompletableFuture รวบรวมคุณลักษณะทั้งหมดของ ListenableFuture ใน Guava และ SettableFuture นอกจากนี้ นิพจน์แลมบ์ดาในตัวยังช่วยให้เข้าใกล้อนาคตของ Scala/Akka มากขึ้นอีกด้วย นี่อาจฟังดูดีเกินจริง แต่อ่านต่อ CompletableFuture มีสองประเด็นหลักที่เหนือกว่าการเรียกกลับ/การแปลงแบบอะซิงโครนัสของ Future ใน ol ซึ่งช่วยให้สามารถตั้งค่าของ CompletableFuture จากเธรดใดๆ ได้ตลอดเวลา
1. แยกและแก้ไขค่าของแพ็คเกจ
บ่อยครั้งที่ฟิวเจอร์สแสดงถึงโค้ดที่ทำงานอยู่ในเธรดอื่น แต่ก็ไม่ได้เป็นเช่นนั้นเสมอไป บางครั้งคุณต้องการสร้างอนาคตเพื่อระบุว่าคุณรู้ว่าจะเกิดอะไรขึ้น เช่น การมาถึงของข้อความ JMS ดังนั้นคุณจึงมีอนาคต แต่ไม่มีงานแบบอะซิงโครนัสที่มีศักยภาพในอนาคต คุณเพียงต้องการทำให้เสร็จสิ้น (แก้ไขแล้ว) เมื่อมีข้อความ JMS ในอนาคตมาถึง ซึ่งขับเคลื่อนโดยเหตุการณ์ ในกรณีนี้ คุณสามารถสร้าง CompletableFuture เพื่อกลับไปยังลูกค้าของคุณได้ และเพียงแค่กรอก() ก็จะปลดล็อกลูกค้าทั้งหมดที่รออนาคตตราบใดที่คุณคิดว่าผลลัพธ์ของคุณพร้อมใช้
ขั้นแรกคุณสามารถสร้าง CompletableFuture ใหม่และมอบให้กับลูกค้าของคุณ:
คัดลอกโค้ดดังต่อไปนี้: Public CompletableFuture<String> Ask() {
สุดท้าย CompletableFuture<String> Future = ใหม่ CompletableFuture<>();
-
กลับอนาคต;
-
โปรดทราบว่าอนาคตนี้ไม่มีการเชื่อมต่อกับ Callable ไม่มีพูลเธรด และไม่ทำงานแบบอะซิงโครนัส หากรหัสไคลเอ็นต์เรียกตอนนี้ว่า ask().get() มันจะบล็อกตลอดไป หากการลงทะเบียนเสร็จสิ้นการติดต่อกลับ จะไม่มีผลใดๆ แล้วกุญแจสำคัญคืออะไร? ตอนนี้คุณสามารถพูดได้ว่า:
คัดลอกโค้ดดังต่อไปนี้: Future.complete("42")
...ในขณะนี้ ลูกค้าทั้งหมด Future.get() จะได้รับผลลัพธ์ของสตริง และจะมีผลทันทีหลังจากการโทรกลับเสร็จสิ้น วิธีนี้จะสะดวกมากเมื่อคุณต้องการแสดงงานแห่งอนาคต และไม่จำเป็นต้องคำนวณงานของเธรดการดำเนินการบางอย่าง CompletableFuture.complete() สามารถเรียกได้เพียงครั้งเดียวเท่านั้น การเรียกครั้งต่อๆ ไปจะถูกละเว้น แต่ก็มีแบ็คดอร์ที่เรียกว่า CompletableFuture.obtrudeValue(...) ซึ่งจะเขียนทับค่าก่อนหน้าของ Future ใหม่ ดังนั้นโปรดใช้ด้วยความระมัดระวัง
บางครั้งคุณต้องการดูว่าจะเกิดอะไรขึ้นเมื่อสัญญาณล้มเหลว ดังที่คุณทราบว่าออบเจ็กต์ Future สามารถจัดการกับผลลัพธ์หรือข้อยกเว้นที่มีอยู่ได้ หากคุณต้องการส่งผ่านข้อยกเว้นเพิ่มเติม คุณสามารถใช้ CompletableFuture.completeExceptionally(ex) (หรือใช้วิธีที่มีประสิทธิภาพมากกว่า เช่น obtrudeException(ex) เพื่อแทนที่ข้อยกเว้นก่อนหน้า) completeExceptionally() ยังปลดล็อคไคลเอนต์ที่รอทั้งหมด แต่คราวนี้ส่งข้อยกเว้นจาก get() เมื่อพูดถึง get() ยังมีเมธอด CompletableFuture.join() ที่มีการเปลี่ยนแปลงเล็กน้อยในการจัดการข้อผิดพลาด แต่โดยรวมก็เหมือนกันหมด ในที่สุดก็มีเมธอด CompletableFuture.getNow(valueIfAbsent) ซึ่งไม่ได้บล็อก แต่จะส่งคืนค่าเริ่มต้นหากอนาคตยังไม่เสร็จสมบูรณ์ ซึ่งทำให้มีประโยชน์มากเมื่อสร้างระบบที่แข็งแกร่งโดยที่เราไม่ต้องการรอนานเกินไป
วิธีคงที่ขั้นสุดท้ายคือการใช้ completeFuture(value) เพื่อส่งคืนอ็อบเจ็กต์ Future ที่เสร็จสมบูรณ์ ซึ่งอาจมีประโยชน์มากเมื่อทดสอบหรือเขียนเลเยอร์อะแดปเตอร์บางเลเยอร์
2. สร้างและรับ CompletableFuture
โอเค การสร้าง CompletableFuture ด้วยตนเองเป็นทางเลือกเดียวของเราใช่ไหม ไม่แน่นอน เช่นเดียวกับ Futures ทั่วไป เราสามารถเชื่อมโยงงานที่มีอยู่ได้ และ CompletableFuture ใช้วิธีการแบบโรงงาน:
คัดลอกรหัสรหัสดังต่อไปนี้:
คงที่ <U> CompletableFuture<U> supplyAsync (ซัพพลายเออร์ <U> ซัพพลายเออร์);
คงที่ <U> CompletableFuture<U> supplyAsync (ซัพพลายเออร์ <U> ซัพพลายเออร์ ผู้ดำเนินการดำเนินการ);
CompletableFuture แบบคงที่ runAsync (Runnable runnable);
CompletableFuture แบบคงที่ runAsync (Runnable runnable, Executor executor);
ตัวดำเนินการเมธอดที่ไม่มีพารามิเตอร์จะลงท้ายด้วย...Async และจะใช้ ForkJoinPool.commonPool() (พูลทั่วไปที่นำมาใช้ใน JDK8) ซึ่งใช้กับเมธอดส่วนใหญ่ในคลาส CompletableFuture runAsync() นั้นเข้าใจง่าย โปรดทราบว่ามันต้องใช้ Runnable ดังนั้นจึงส่งคืน CompletableFuture<Void> เนื่องจาก Runnable ไม่ส่งคืนค่าใด ๆ หากคุณต้องการจัดการการดำเนินการแบบอะซิงโครนัสและส่งคืนผลลัพธ์ ให้ใช้ Supplier<U>:
คัดลอกรหัสรหัสดังต่อไปนี้:
Final CompletableFuture<String> Future = CompletableFuture.supplyAsync(ซัพพลายเออร์ใหม่<String>() {
@แทนที่
สตริงสาธารณะรับ () {
//...วิ่งยาวๆ...
กลับ "42";
-
} ผู้ดำเนินการ);
แต่อย่าลืมว่ายังมี lambdas expressions ใน Java 8!
คัดลอกรหัสรหัสดังต่อไปนี้:
FinalCompletableFuture <สตริง> อนาคต = CompletableFuture.supplyAsync(() -> {
//...วิ่งยาวๆ...
กลับ "42";
} ผู้ดำเนินการ);
หรือ:
คัดลอกรหัสรหัสดังต่อไปนี้:
สุดท้าย CompletableFuture<String> อนาคต =
CompletableFuture.supplyAsync(() -> longRunningTask (พารามิเตอร์), ผู้ดำเนินการ);
แม้ว่าบทความนี้จะไม่เกี่ยวกับ lambda แต่ฉันก็ยังใช้นิพจน์ lambda ค่อนข้างบ่อย
3. การเปลี่ยนแปลงและการดำเนินการกับ CompletableFuture (จากนั้นสมัคร)
ฉันบอกว่า CompletableFuture ดีกว่า Future แต่คุณไม่รู้ว่าทำไม? พูดง่ายๆ เพราะ CompletableFuture คืออะตอมและปัจจัย สิ่งที่ฉันพูดไปมันไม่มีประโยชน์เหรอ? ทั้ง Scala และ JavaScript ช่วยให้คุณสามารถลงทะเบียนการโทรกลับแบบอะซิงโครนัสเมื่ออนาคตเสร็จสิ้น และเราไม่ต้องรอและบล็อกจนกว่าจะพร้อม เราพูดง่ายๆ ว่า: เมื่อคุณเรียกใช้ฟังก์ชันนี้ ผลลัพธ์จะปรากฏขึ้น นอกจากนี้เรายังสามารถซ้อนฟังก์ชันเหล่านี้ รวมหลายฟิวเจอร์สเข้าด้วยกัน เป็นต้น ตัวอย่างเช่น ถ้าเราแปลงจาก String เป็น Integer เราสามารถแปลงจาก CompletableFuture เป็น CompletableFuture<Integer โดยไม่มีการเชื่อมโยง ทำได้ผ่านทาง thenApply():
คัดลอกรหัสรหัสดังต่อไปนี้:
<U> CompletableFuture<U> จากนั้นนำไปใช้ (ฟังก์ชัน<? super T,? ขยาย U> fn);
<U> CompletableFuture<U> จากนั้นใช้Async(ฟังก์ชัน<? super T,? ขยาย U> fn);
<U> CompletableFuture<U> จากนั้นApplyAsync(Function<? super T,? ขยาย U> fn, ตัวดำเนินการ Executor);<p></p>
<p>ตามที่กล่าวไว้... เวอร์ชัน Async ให้การดำเนินการส่วนใหญ่บน CompletableFuture ดังนั้นฉันจะข้ามการดำเนินการเหล่านี้ไปในส่วนต่อๆ ไป โปรดจำไว้ว่า วิธีแรกจะเรียกวิธีการนั้นในเธรดเดียวกันกับที่อนาคตจะเสร็จสมบูรณ์ ในขณะที่อีกสองวิธีที่เหลือจะเรียกวิธีการแบบอะซิงโครนัสในเธรดพูลที่ต่างกัน
มาดูขั้นตอนการทำงานของ thenApply():</p> กัน
<p><pre>
CompletableFuture<String> f1 = //...
CompletableFuture<จำนวนเต็ม> f2 = f1.thenApply(จำนวนเต็ม::parseInt);
CompletableFuture<สองเท่า> f3 = f2.thenApply(r -> r * r * Math.PI);
</p>
หรือในแถลงการณ์:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompleteableFuture<สองเท่า> f3 =
f1.thenApply(จำนวนเต็ม::parseInt).thenApply(r -> r * r * Math.PI);
ที่นี่ คุณจะเห็นการแปลงลำดับจากสตริงเป็นจำนวนเต็มเป็นสองเท่า แต่ที่สำคัญที่สุด การเปลี่ยนแปลงเหล่านี้ไม่ได้ดำเนินการทันทีหรือหยุดลง การแปลงเหล่านี้จะไม่ดำเนินการทันทีหรือหยุด พวกเขาจำโปรแกรมที่พวกเขาดำเนินการเมื่อ f1 ดั้งเดิมเสร็จสมบูรณ์ หากการแปลงบางอย่างใช้เวลานานมาก คุณสามารถจัดเตรียม Executor ของคุณเองเพื่อรันการแปลงแบบอะซิงโครนัสได้ โปรดทราบว่าการดำเนินการนี้เทียบเท่ากับแผนที่เอกภาคใน Scala
4. เรียกใช้โค้ดที่เสร็จสมบูรณ์ (แล้วยอมรับ/แล้วเรียกใช้)
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<Void> จากนั้นยอมรับ (บล็อกผู้บริโภค <? super T>);
CompletableFuture<Void> จากนั้นเรียกใช้ (การดำเนินการที่รันได้);
มีวิธีขั้นตอน "ขั้นสุดท้าย" ทั่วไปสองวิธีในไปป์ไลน์ในอนาคต สิ่งเหล่านี้เตรียมไว้เมื่อคุณใช้ค่าในอนาคต เมื่อ thenAccept() ระบุค่าสุดท้ายแล้ว Run จะดำเนินการ Runnable ซึ่งไม่มีวิธีคำนวณค่าด้วยซ้ำ ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
Future.thenAcceptAsync(dbl -> log.debug("ผลลัพธ์: {}", dbl), ผู้ดำเนินการ);
log.debug("ดำเนินการต่อ");
...ตัวแปร Async มีให้เลือกใช้สองวิธี ได้แก่ ตัวดำเนินการโดยนัยและชัดเจน และฉันจะไม่เน้นวิธีนี้มากเกินไป
วิธีการ thenAccept()/thenRun() จะไม่บล็อก (แม้ว่าจะไม่มีตัวดำเนินการที่ชัดเจนก็ตาม) พวกมันเป็นเหมือนผู้ฟัง/ตัวจัดการเหตุการณ์ ซึ่งจะดำเนินการเป็นระยะเวลาหนึ่งเมื่อคุณเชื่อมต่อกับอนาคต ข้อความ "ต่อเนื่อง" จะปรากฏขึ้นทันทีแม้อนาคตจะยังไม่สมบูรณ์ด้วยซ้ำ
5. การจัดการข้อผิดพลาดของ CompletableFuture เดียว
จนถึงตอนนี้เราได้หารือกันเฉพาะผลการคำนวณเท่านั้น แล้วข้อยกเว้นล่ะ? เราสามารถจัดการพวกมันแบบอะซิงโครนัสได้หรือไม่? แน่นอน!
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<String> ปลอดภัย =
Future.Exceptionally(เช่น -> "เรามีปัญหา: " + ex.getMessage());
เมื่อ ยกเว้น () ยอมรับฟังก์ชัน อนาคตดั้งเดิมจะถูกเรียกให้ส่งข้อยกเว้น เราจะมีโอกาสแปลงข้อยกเว้นนี้เป็นค่าบางอย่างที่เข้ากันได้กับประเภท Future เพื่อกู้คืน การแปลง safeFurther จะไม่ทำให้เกิดข้อยกเว้นอีกต่อไป แต่จะส่งกลับค่าสตริงจากฟังก์ชันที่ให้ฟังก์ชันการทำงานแทน
แนวทางที่ยืดหยุ่นมากขึ้นคือให้ handle() ยอมรับฟังก์ชันที่ได้รับผลลัพธ์หรือข้อยกเว้นที่ถูกต้อง:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<Integer> safe = Future.handle((ตกลง อดีต) -> {
ถ้า (ตกลง != null) {
กลับ Integer.parseInt (ตกลง);
} อื่น {
log.warn("ปัญหา" เช่น);
กลับ -1;
-
-
handle() จะถูกเรียกเสมอ และผลลัพธ์และข้อยกเว้นไม่เป็นค่าว่าง นี่คือกลยุทธ์แบบครบวงจรในที่เดียว
6. รวม CompletableFutures สองรายการเข้าด้วยกัน
CompletableFuture เป็นหนึ่งในกระบวนการอะซิงโครนัสนั้นยอดเยี่ยม แต่มันแสดงให้เห็นจริงๆ ว่ามันทรงพลังเพียงใดเมื่อฟิวเจอร์สหลายรายการรวมกันในรูปแบบต่างๆ
7. รวม (ลิงก์) สองฟิวเจอร์สนี้ (แล้วเขียน())
บางครั้งคุณต้องการรันตามมูลค่าของอนาคต (เมื่อพร้อม) แต่ฟังก์ชันนี้จะส่งคืนอนาคตด้วย CompletableFuture มีความยืดหยุ่นเพียงพอที่จะเข้าใจว่าผลลัพธ์ของฟังก์ชันของเราควรใช้เป็นอนาคตระดับบนสุด เมื่อเปรียบเทียบกับ CompletableFuture<CompletableFuture> วิธีการ thenCompose() เทียบเท่ากับ flatMap ของ Scala:
คัดลอกรหัสรหัสดังต่อไปนี้:
<U> CompletableFuture<U> จากนั้นเขียน (ฟังก์ชัน<? super T,CompletableFuture<U>> fn);
...ยังมีรูปแบบ Async ให้เลือกด้วย ในตัวอย่างต่อไปนี้ ให้สังเกตประเภทและความแตกต่างระหว่าง thenApply()(map) และ thenCompose()(flatMap) อย่างระมัดระวัง เมื่อใช้เมธอดการคำนวณความเกี่ยวข้อง () CompletableFuture จะถูกส่งกลับ:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<เอกสาร> docFuture = //...
CompletableFuture<CompletableFuture<สองเท่า>> f =
docFuture.thenApply(นี่::calculateRelevance);
CompletableFuture<สองเท่า> ความเกี่ยวข้องFuture =
docFuture.thenCompose(นี่::calculateRelevance);
-
CompletableFuture ส่วนตัว <Double> คำนวณความเกี่ยวข้อง (เอกสารเอกสาร) //...
thenCompose() เป็นวิธีการสำคัญที่ช่วยให้สร้างไปป์ไลน์ที่แข็งแกร่งและอะซิงโครนัสได้โดยไม่ต้องบล็อกและรอขั้นตอนกลาง
8. มูลค่าการแปลงของสองฟิวเจอร์ส (thenCombine())
เมื่อ thenCompose() ถูกใช้เพื่อโยงอนาคตที่ขึ้นอยู่กับอีกอันหนึ่งแล้วรวมเข้าด้วยกัน เมื่อทั้งสองเสร็จสมบูรณ์ มันจะรวมสองอนาคตที่เป็นอิสระเข้าด้วยกัน:
คัดลอกรหัสรหัสดังต่อไปนี้:
<U,V> CompletableFuture<V> จากนั้นรวม (CompletableFuture<? ขยาย U> อื่น ๆ BiFunction<? super T,? super U,? ขยาย V> fn)
...ตัวแปร Async ก็พร้อมใช้งานเช่นกัน สมมติว่าคุณมี CompletableFutures สองตัว ตัวหนึ่งกำลังโหลดลูกค้า และอีกตัวกำลังโหลดร้านค้าล่าสุด พวกมันเป็นอิสระจากกันโดยสิ้นเชิง แต่เมื่อเสร็จแล้ว คุณต้องการใช้ค่าของมันในการคำนวณเส้นทาง นี่เป็นตัวอย่างที่ขาดไม่ได้:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<ลูกค้า> customerFuture = loadCustomerDetails(123);
CompletableFuture<ร้านค้า> shopFuture = ร้านค้าที่ใกล้ที่สุด();
CompletableFuture<เส้นทาง> เส้นทางอนาคต =
customerFuture.thenCombine(shopFuture, (ลูกค้า, ร้านค้า) -> findRoute (ลูกค้า, ร้านค้า));
-
เส้นทางส่วนตัว findRoute(ลูกค้าลูกค้า ร้านค้า) //...
โปรดทราบว่าใน Java 8 คุณสามารถแทนที่การอ้างอิงเป็น this::findRoute วิธีการนี้ด้วย (cust, shop) -> findRoute(cust, shop):
คัดลอกรหัสรหัสดังต่อไปนี้:
customerFuture.thenCombine(shopFuture นี่::findRoute);
ดังที่คุณทราบ เรามี customerFuture และ shopFuture จากนั้นเส้นทางในอนาคตจะล้อมรอบพวกเขาและ "รอ" เพื่อให้เสร็จสมบูรณ์ เมื่อพร้อมแล้ว มันจะเรียกใช้ฟังก์ชันที่เราให้ไว้เพื่อรวมผลลัพธ์ทั้งหมด (findRoute()) RouteFuture นี้จะเสร็จสมบูรณ์เมื่อฟิวเจอร์สพื้นฐานทั้งสองเสร็จสมบูรณ์และ findRoute() เสร็จสมบูรณ์เช่นกัน
9. รอให้ CompletableFutures ทั้งหมดเสร็จสมบูรณ์
หากแทนที่จะสร้าง CompletableFuture ใหม่โดยเชื่อมต่อผลลัพธ์ทั้งสองนี้ เราเพียงต้องการได้รับการแจ้งเตือนเมื่อเสร็จสมบูรณ์ เราสามารถใช้ชุดวิธีการ thenAcceptBoth()/runAfterBoth() ได้ (...ตัวแปร Async ก็พร้อมใช้งานเช่นกัน) ทำงานคล้ายกับ thenAccept() และ thenRun() แต่รอสองอนาคตแทนที่จะเป็นหนึ่ง:
คัดลอกรหัสรหัสดังต่อไปนี้:
<U> CompletableFuture<Void> จากนั้นAcceptBoth(CompletableFuture<? ขยาย U> อื่น ๆ BiConsumer<? super T,? super U> บล็อก)
CompletableFuture<Void> runAfterBoth (CompletableFuture<?> อื่น ๆ การดำเนินการที่รันได้)
ลองนึกภาพตัวอย่างข้างต้น แทนที่จะสร้าง CompletableFuture ใหม่ คุณเพียงต้องการส่งเหตุการณ์บางอย่างหรือรีเฟรช GUI ทันที สามารถทำได้ง่ายๆ: thenAcceptBoth():
คัดลอกรหัสรหัสดังต่อไปนี้:
customerFuture.thenAcceptBoth (shopFuture, (ลูกค้า, ร้านค้า) -> {
เส้นทางเส้นทางสุดท้าย = findRoute (cust, ร้านค้า);
//รีเฟรช GUI พร้อมเส้นทาง
-
ฉันหวังว่าฉันจะผิด แต่บางทีบางคนอาจถามตัวเองว่า: ทำไมฉันไม่สามารถปิดกั้นอนาคตทั้งสองนี้ได้? ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
อนาคต < ลูกค้า > customerFuture = loadCustomerDetails (123);
อนาคต<ร้านค้า> shopFuture = ร้านค้าที่ใกล้ที่สุด();
findRoute(customerFuture.get(), shopFuture.get());
แน่นอนคุณสามารถทำได้ แต่จุดที่สำคัญที่สุดคือ CompletableFuture ช่วยให้เกิดความอะซิงโครนัสได้ มันเป็นโมเดลการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์ แทนที่จะบล็อกและรอผลลัพธ์อย่างใจจดใจจ่อ ในทางปฏิบัติแล้ว โค้ดสองส่วนด้านบนนั้นเทียบเท่ากัน แต่ส่วนหลังไม่จำเป็นต้องใช้เธรดเพื่อดำเนินการ
10. รอ CompletableFuture อันแรกเพื่อทำงานให้เสร็จสิ้น
สิ่งที่น่าสนใจอีกประการหนึ่งคือ CompletableFutureAPI สามารถรอให้อนาคตแรก (ตรงข้ามกับทั้งหมด) เสร็จสมบูรณ์ได้ วิธีนี้จะสะดวกมากเมื่อคุณได้รับผลลัพธ์ของงานประเภทเดียวกันสองงาน คุณสนใจแต่เวลาตอบสนองเท่านั้น และไม่มีงานใดที่มีความสำคัญ วิธีการ API (…ตัวแปร Async ก็มีให้เลือกเช่นกัน):
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<Void> ยอมรับทั้ง (CompletableFuture<? ขยาย T> อื่น ๆ ผู้บริโภค <? super T> บล็อก)
CompletableFuture<Void> runAfterEither (CompletableFuture<?> อื่น ๆ การดำเนินการที่รันได้)
ตามตัวอย่าง คุณมีสองระบบที่สามารถบูรณาการได้ อันหนึ่งมีเวลาตอบสนองโดยเฉลี่ยน้อยกว่าแต่มีค่าเบี่ยงเบนมาตรฐานสูง ส่วนอีกอันโดยทั่วไปจะช้ากว่าแต่คาดเดาได้ดีกว่า เพื่อให้ได้ประโยชน์สูงสุดจากทั้งสองโลก (ประสิทธิภาพและความสามารถในการคาดเดาได้) คุณสามารถเรียกทั้งสองระบบพร้อมกันและรอจนกว่าระบบใดจะเสร็จสิ้นก่อน โดยปกตินี่จะเป็นระบบแรก แต่เมื่อความคืบหน้าช้าลง ระบบที่สองสามารถดำเนินการให้เสร็จสิ้นภายในเวลาที่ยอมรับได้:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<String> รวดเร็ว = fetchFast();
CompletableFuture<String> คาดเดาได้ = fetchPredictable();
fast.acceptEither (คาดเดาได้ s -> {
System.out.println("ผลลัพธ์: " + s);
-
s แสดงถึงสตริงที่ได้รับจาก fetchFast() หรือ fetchPredictable() เราไม่จำเป็นต้องรู้หรือสนใจ
11. แปลงระบบแรกโดยสมบูรณ์
ApplyToEither() ถือเป็นรุ่นก่อนหน้าของ AcceptEither() เมื่ออนาคตทั้งสองกำลังจะเสร็จสมบูรณ์ อนาคตเพียงเรียกข้อมูลโค้ดบางส่วนแล้ว ApplyToEither() จะส่งกลับอนาคตใหม่ เมื่ออนาคตเริ่มต้นทั้งสองนี้เสร็จสมบูรณ์ อนาคตใหม่ก็จะเสร็จสมบูรณ์เช่นกัน API ค่อนข้างคล้ายกัน (...ตัวแปร Async ก็มีให้เช่นกัน):
คัดลอกรหัสดังนี้:<U> CompletableFuture<U> ApplyToEither(CompletableFuture<? ขยาย T> อื่น ๆ ฟังก์ชัน<? super T,U> fn)
ฟังก์ชัน fn เพิ่มเติมนี้สามารถดำเนินการให้เสร็จสิ้นได้เมื่อมีการเรียกอนาคตแรก ฉันไม่แน่ใจว่าจุดประสงค์ของวิธีการพิเศษนี้คืออะไร เพราะทุกคนสามารถใช้: fast.applyToEither(predictable).thenApply(fn) เนื่องจากเราติดอยู่กับ API นี้ แต่เราไม่ต้องการฟังก์ชันพิเศษสำหรับแอปพลิเคชันจริงๆ ฉันจะใช้ตัวยึดตำแหน่ง Function.identity():
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture<String> รวดเร็ว = fetchFast();
CompletableFuture<String> คาดเดาได้ = fetchPredictable();
CompletableFuture<String> firstDone =
fast.applyToEither(คาดเดาได้, Function.<String>identity());
อนาคตที่เสร็จสมบูรณ์ครั้งแรกสามารถดำเนินการได้ โปรดทราบว่าจากมุมมองของลูกค้า ทั้งสองอนาคตถูกซ่อนไว้เบื้องหลัง firstDone ลูกค้าเพียงแค่รออนาคตให้เสร็จสิ้น และใช้ ApplyToEither() เพื่อแจ้งให้ลูกค้าทราบเมื่องานสองงานแรกเสร็จสมบูรณ์
12. CompletableFuture ที่มีหลายชุด
ตอนนี้เรารู้วิธีรอให้สองฟิวเจอร์สเสร็จสมบูรณ์ (โดยใช้ thenCombine()) และอันแรกที่จะเสร็จสมบูรณ์ (applyToEither()) แต่มันสามารถขยายไปสู่อนาคตจำนวนเท่าใดก็ได้? ใช้วิธีการช่วยเหลือแบบคงที่:
คัดลอกรหัสรหัสดังต่อไปนี้:
CompletableFuture แบบคงที่ <โมฆะ < allOf (CompletableFuture<?<... cfs)
CompletableFuture แบบคงที่ <วัตถุ < anyOf (CompletableFuture<?<... cfs)
allOf() ใช้อาร์เรย์ของอนาคตและส่งกลับอนาคต (รออุปสรรคทั้งหมด) เมื่ออนาคตที่เป็นไปได้ทั้งหมดเสร็จสมบูรณ์ ในทางกลับกัน anyOf() จะรออนาคตที่เป็นไปได้ที่เร็วที่สุด โปรดดูประเภททั่วไปของฟิวเจอร์สที่ส่งคืน เราจะเน้นไปที่ปัญหานี้ในบทความถัดไป
สรุป
เราได้สำรวจ CompletableFuture API ทั้งหมดแล้ว ฉันเชื่อว่าสิ่งนี้จะไม่มีวันเปลี่ยนแปลง ดังนั้นในบทความถัดไป เราจะดูที่การใช้งานของโปรแกรมรวบรวมข้อมูลเว็บแบบธรรมดาอื่นโดยใช้วิธี CompletableFuture และนิพจน์แลมบ์ดา Java 8 เราจะดูที่ CompletableFuture ด้วย