Giới thiệu
Chúng ta đã xem xét cách tạo chủ đề trong
phần đầu tiên . Chúng ta hãy nhớ lại.
Một luồng là
Thread
thứ gì đó chạy trong đó
run
, vì vậy hãy sử dụng
trình biên dịch trực tuyến java tutorialspoint và thực thi đoạn mã sau:
public class HelloWorld {
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello World");
};
new Thread(task).start();
}
}
Đây có phải là tùy chọn duy nhất để chạy một tác vụ trong một chuỗi không?
java.util.concurrent.Callable
Hóa ra
java.lang.Runnable có một người anh trai tên là
java.util.concurrent.Callable và anh ấy sinh ra ở Java 1.5. Sự khác biệt là gì? Nếu chúng ta xem xét kỹ hơn JavaDoc của giao diện này, chúng ta sẽ thấy rằng, không giống như
Runnable
, giao diện mới khai báo một phương thức
call
trả về một kết quả. Ngoài ra, theo mặc định, nó sẽ ném Ngoại lệ. Nghĩa là, nó giúp chúng ta không cần phải viết
try-catch
các khối cho các ngoại lệ được kiểm tra. Cũng không tệ lắm phải không? Bây giờ chúng ta có
Runnable
một nhiệm vụ mới thay thế:
Callable task = () -> {
return "Hello, World!";
};
Nhưng phải làm gì với nó? Tại sao chúng ta lại cần một tác vụ chạy trên một luồng trả về kết quả? Rõ ràng, trong tương lai chúng ta mong đợi nhận được kết quả của những hành động sẽ được thực hiện trong tương lai. Tương Lai trong tiếng Anh – Future. Và có một giao diện có cùng tên:
java.util.concurrent.Future
java.util.concurrent.Future
Giao diện
java.util.concurrent.Future mô tả một API để làm việc với các tác vụ mà chúng tôi dự định đạt được kết quả trong tương lai: phương pháp lấy kết quả, phương pháp kiểm tra trạng thái. Chúng tôi
Future
quan tâm đến việc triển khai
java.util.concurrent.FutureTask của nó . Nghĩa là
Task
, đây là những gì sẽ được thực thi trong
Future
. Điều thú vị về việc triển khai này là nó triển khai và
Runnable
. Bạn có thể coi đây là một loại bộ chuyển đổi của mô hình cũ để làm việc với các tác vụ trong luồng và mô hình mới (mới theo nghĩa là nó đã xuất hiện trong java 1.5). Đây là một ví dụ:
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());
}
}
Như có thể thấy từ ví dụ, sử dụng phương pháp này chúng ta thu được
get
kết quả từ bài toán
task
.
(!)Quan trọng, tại thời điểm kết quả thu được bằng phương thức này,
get
việc thực thi sẽ trở nên đồng bộ. Bạn nghĩ cơ chế nào sẽ được sử dụng ở đây? Đúng vậy, không có khối đồng bộ hóa - do đó chúng ta sẽ thấy
WAITING trong JVisualVM không phải là
monitor
or
wait
, mà là chính khối đó
park
(vì cơ chế được sử dụng
LockSupport
).
Giao diện chức năng
Tiếp theo chúng ta sẽ nói về các lớp từ Java 1.8, vì vậy sẽ rất hữu ích nếu giới thiệu ngắn gọn. Chúng ta hãy xem đoạn mã sau:
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);
}
};
Có rất nhiều mã không cần thiết phải không? Mỗi lớp được khai báo thực hiện một chức năng duy nhất, nhưng để mô tả nó, chúng ta sử dụng một loạt mã phụ trợ không cần thiết. Và các nhà phát triển Java cũng nghĩ như vậy. Do đó, họ đã giới thiệu một bộ “giao diện chức năng” (
@FunctionalInterface
) và quyết định rằng bây giờ chính Java sẽ “nghĩ ra” mọi thứ cho chúng ta, ngoại trừ những thứ quan trọng:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier
- các nhà cung cấp. Nó không có tham số, nhưng nó trả về một cái gì đó, tức là nó cung cấp cho nó.
Consumer
- người tiêu dùng. Nó lấy thứ gì đó làm đầu vào (tham số s) và thực hiện điều gì đó với nó, tức là tiêu thụ thứ gì đó. Có một chức năng khác. Nó lấy một cái gì đó làm đầu vào (tham số
s
), thực hiện một cái gì đó và trả về một cái gì đó. Như chúng ta thấy, thuốc generic được sử dụng tích cực. Nếu không chắc chắn, bạn có thể ghi nhớ chúng và đọc “
Lý thuyết về generics trong Java hoặc cách đặt dấu ngoặc đơn trong thực tế ”.
Hoàn thànhTương lai
Thời gian trôi qua, Java 1.8 đã giới thiệu một lớp mới gọi là
CompletableFuture
. Nó triển khai giao diện
Future
, nghĩa là giao diện của chúng ta
task
sẽ được thực thi trong tương lai và chúng ta có thể thực thi
get
và nhận được kết quả. Nhưng anh ấy cũng thực hiện một số
CompletionStage
. Từ bản dịch, mục đích của nó đã rõ ràng: đó là một Giai đoạn nhất định của một loại tính toán nào đó. Bạn có thể tìm thấy phần giới thiệu ngắn gọn về chủ đề này trong phần tổng quan "
Giới thiệu về CompleteionStage và CompleteableFuture ". Hãy đi thẳng vào vấn đề. Hãy xem danh sách các phương thức tĩnh có sẵn để giúp chúng ta bắt đầu:
Dưới đây là các tùy chọn để sử dụng chúng:
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 "Значение";
});
}
}
Nếu chạy mã này, chúng ta sẽ thấy việc tạo đó
CompletableFuture
liên quan đến việc bắt đầu toàn bộ chuỗi. Do đó, mặc dù có một số điểm tương đồng với SteamAPI từ Java8, nhưng đây chính là điểm khác biệt giữa các cách tiếp cận này. Ví dụ:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
System.out.println("Executed");
return value.toUpperCase();
});
Đây là một ví dụ về Java 8 Stream Api (bạn có thể đọc thêm về nó tại đây "
Hướng dẫn API luồng Java 8 trong hình ảnh và ví dụ "). Nếu bạn chạy mã này, nó
Executed
sẽ không hiển thị. Nghĩa là, khi tạo một luồng trong Java, luồng đó không bắt đầu ngay lập tức mà đợi cho đến khi cần một giá trị từ luồng đó. Nhưng
CompletableFuture
nó bắt đầu thực thi chuỗi ngay lập tức mà không cần đợi nó được yêu cầu về giá trị được tính toán. Tôi nghĩ điều quan trọng là phải hiểu điều này. Vậy là chúng ta có CompleteableFuture. Làm thế nào chúng ta có thể tạo ra một chuỗi và chúng ta có phương tiện gì? Hãy nhớ lại các giao diện chức năng mà chúng ta đã viết trước đó.
- Chúng ta có một hàm (
Function
) nhận vào A và trả về B. Nó có một phương thức duy nhất - apply
(áp dụng).
- Chúng ta có một người tiêu dùng (
Consumer
) chấp nhận A và không trả lại gì ( Void ). Nó chỉ có một phương pháp - accept
(chấp nhận).
- Chúng tôi có mã chạy trên một chuỗi
Runnable
không chấp nhận hoặc trả lại. Nó có một phương thức duy nhất - run
(chạy).
Điều thứ hai cần nhớ là
CompletalbeFuture
trong công việc của mình, nó sử dụng
Runnable
người tiêu dùng và chức năng. Vì điều này, bạn luôn có thể nhớ rằng bạn
CompletableFuture
có thể làm điều này:
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);
}
Phương thức
thenRun
có
thenApply
phiên bản .
thenAccept
_
Async
Điều này có nghĩa là các giai đoạn này sẽ được thực thi trong một luồng mới. Nó sẽ được lấy từ một hồ bơi đặc biệt nên không biết trước nó sẽ có dòng chảy như thế nào, mới hay cũ. Tất cả phụ thuộc vào độ khó của nhiệm vụ. Ngoài những phương pháp này, còn có ba khả năng thú vị hơn. Để rõ ràng, hãy tưởng tượng rằng chúng ta có một dịch vụ nhất định nhận tin nhắn từ đâu đó và cần có thời gian:
public static class NewsService {
public static String getMessage() {
try {
Thread.currentThread().sleep(3000);
return "Message";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Bây giờ, hãy xem xét các tính năng khác của
CompletableFuture
. Chúng ta có thể kết hợp kết quả này
CompletableFuture
với kết quả của một kết quả khác
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();
Điều đáng chú ý là theo mặc định, các luồng sẽ là các luồng daemon, vì vậy để rõ ràng
get
, chúng ta sử dụng để chờ kết quả. Và chúng ta không chỉ có thể kết hợp (kết hợp) mà còn có thể return
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
Ở đây tôi muốn lưu ý rằng để cho ngắn gọn, phương pháp này đã được sử dụng
CompletableFuture.completedFuture
. Phương thức này không tạo ra một luồng mới, do đó phần còn lại của chuỗi sẽ được thực thi trong cùng một luồng mà nó được gọi
completedFuture
. Ngoài ra còn có một phương pháp
thenAcceptBoth
. Nó rất giống với
accept
, nhưng nếu
thenAccept
nó chấp nhận
consumer
, thì
thenAcceptBoth
nó chấp nhận một
CompletableStage
+ khác làm đầu vào
BiConsumer
, tức là
consumer
chấp nhận 2 nguồn làm đầu vào chứ không phải một. Có một khả năng thú vị khác với từ này
Either
:
Các phương thức này chấp nhận một phương án thay thế
CompletableStage
và sẽ được thực thi trên phương thức
CompletableStage
được thực thi trước. Và tôi muốn kết thúc bài đánh giá này bằng một tính năng thú vị khác
CompletableFuture
- xử lý lỗi.
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
.thenAccept(val -> System.out.println(val));
Mã này sẽ không làm gì cả, bởi vì... một ngoại lệ sẽ được ném ra và sẽ không có gì xảy ra. Nhưng nếu chúng ta bỏ ghi chú
exceptionally
thì chúng ta sẽ xác định hành vi.
CompletableFuture
Tôi cũng khuyên bạn nên xem video sau về chủ đề này :
Theo quan điểm khiêm tốn của tôi, những video này là một trong những video trực quan nhất trên Internet. Họ phải nói rõ tất cả hoạt động như thế nào, chúng tôi có kho vũ khí gì và tại sao tất cả đều cần thiết.
Phần kết luận
Hy vọng bây giờ bạn đã rõ cách sử dụng các luồng để truy xuất các phép tính sau khi chúng được tính toán. Tài liệu bổ sung:
#Viacheslav
GO TO FULL VERSION