การแนะนำ
เราได้ดูวิธีการสร้างเธรด ใน
ส่วนแรก แล้ว เรามารำลึกกันอีกครั้ง
เธรดคือ
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.อนาคต
อินเทอร์เฟซ
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
การดำเนินการจะกลายเป็นแบบซิงโครนัส คุณคิดว่าจะใช้กลไกอะไรที่นี่? ถูกต้อง ไม่มีบล็อกการซิงโครไนซ์ - ดังนั้น เราจะเห็น
WAITINGใน 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) และทำอะไรบางอย่างกับมัน นั่นคือ กินอะไรบางอย่าง มีฟังก์ชั่นอื่น ใช้บางสิ่งเป็นอินพุต (พารามิเตอร์
s
) ทำบางสิ่งและส่งคืนบางสิ่ง ดังที่เราเห็นมีการใช้ยาชื่อสามัญอย่างแข็งขัน หากคุณไม่แน่ใจ คุณสามารถจำไว้และอ่าน “
ทฤษฎีของยาชื่อสามัญในภาษา Java หรือวิธีใส่วงเล็บในทางปฏิบัติ ”
อนาคตที่สมบูรณ์
เมื่อเวลาผ่านไป Java 1.8 ได้เปิดตัวคลาสใหม่ที่เรียกว่า
CompletableFuture
. มันใช้อินเทอร์เฟซ
Future
ซึ่งหมายความว่าของเรา
task
จะถูกดำเนินการในอนาคตและเราสามารถดำเนินการ
get
และรับผลลัพธ์ได้ แต่เขาก็ดำเนินการบางอย่าง
CompletionStage
ด้วย จากการแปลวัตถุประสงค์ก็ชัดเจนแล้ว: เป็นขั้นตอนหนึ่งของการคำนวณบางประเภท ข้อมูลเบื้องต้นเกี่ยวกับหัวข้อนี้โดยย่อสามารถพบได้ในภาพรวม "
บทนำสู่ขั้นตอนที่เสร็จสมบูรณ์และอนาคตที่สมบูรณ์ " มาตรงประเด็นกันดีกว่า มาดูรายการวิธีการคงที่ที่มีอยู่เพื่อช่วยเราในการเริ่มต้น:
ต่อไปนี้คือตัวเลือกสำหรับการใช้งาน:
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
บ่งบอกถึงการเปิดตัวของ chain ทั้งหมด ดังนั้น แม้ว่า 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
จะเริ่มเชนเพื่อดำเนินการทันที โดยไม่ต้องรอให้ถามถึงค่าที่คำนวณได้ ฉันคิดว่าสิ่งสำคัญคือต้องเข้าใจสิ่งนี้ ดังนั้นเราจึงมี CompletableFuture เราจะสร้างห่วงโซ่ได้อย่างไร และเรามีวิธีการอย่างไร? จำเกี่ยวกับอินเทอร์เฟซการทำงานที่เราเขียนไว้ก่อนหน้านี้
- เรามีฟังก์ชัน (
Function
) ที่รับ A และส่งกลับ B โดยมีวิธีการเดียวคือ - 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.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();
เป็นที่น่าสังเกตว่าตามค่าเริ่มต้น เธรดจะเป็นเธรด daemon ดังนั้นเพื่อความชัดเจน
get
เราจึงใช้เพื่อรอผลลัพธ์ และเราไม่เพียงแต่สามารถรวม (รวม) เท่านั้น แต่ยังส่งคืน
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
ในที่นี้ฉันอยากจะทราบว่าเพื่อความกระชับ มีการใช้วิธี
CompletableFuture.completedFuture
นี้ เมธอดนี้ไม่ได้สร้างเธรดใหม่ ดังนั้นเชนที่เหลือจะถูกดำเนินการในเธรดเดียวกันกับที่ถูกเรียก
completedFuture
ว่า นอกจากนี้ยังมีวิธี
thenAcceptBoth
การ มันคล้ายกันมากกับ
accept
แต่ถ้า
thenAccept
ยอมรับมันก็จะยอมรับอีก+ เป็นอินพุต
consumer
นั่นคือซึ่งยอมรับ 2 แหล่งเป็นอินพุต ไม่ใช่แหล่งเดียว มีความเป็นไปได้ที่น่าสนใจอีกประการหนึ่งสำหรับคำว่า: วิธีการเหล่านี้ยอมรับทางเลือกอื่นและจะดำเนินการกับวิธีที่ถูกดำเนินการก่อน และฉันอยากจะจบการรีวิวนี้ด้วยฟีเจอร์ที่น่าสนใจอีกอย่างหนึ่งนั่นก็คือ การจัดการข้อผิดพลาด
thenAcceptBoth
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));
รหัสนี้จะไม่ทำอะไรเลยเพราะ... ข้อยกเว้นจะถูกส่งออกไปและจะไม่มีอะไรเกิดขึ้น แต่ถ้าเราไม่แสดงความคิดเห็น
exceptionally
เราก็จะกำหนดพฤติกรรมนั้น
CompletableFuture
ฉันขอแนะนำให้ดูวิดีโอต่อไปนี้ ในหัวข้อนี้ :
ในความเห็นอันต่ำต้อยของฉัน วิดีโอเหล่านี้เป็นวิดีโอที่มีภาพมากที่สุดบนอินเทอร์เน็ต มันควรจะชัดเจนจากพวกเขาว่ามันทำงานอย่างไร เรามีคลังแสงอะไรบ้าง และทำไมมันถึงจำเป็นทั้งหมด
บทสรุป
หวังว่าตอนนี้คงชัดเจนแล้วว่าเธรดสามารถใช้เพื่อดึงข้อมูลการคำนวณได้อย่างไรหลังจากคำนวณแล้ว วัสดุเพิ่มเติม:
#เวียเชสลาฟ
GO TO FULL VERSION