Cách đây không lâu, tôi đã viết một bài về các interface Callable và Future trong Java, cho phép chúng ta vừa tận dụng lợi ích xử lý song song của luồng (thread), vừa có khả năng trả về giá trị cho chương trình gọi.

FutureTask trong Java là một triển khai cụ thể cơ bản của interface Future và cung cấp khả năng xử lý bất đồng bộ. Lớp này có các phương thức để khởi chạy hoặc hủy một tác vụ, đồng thời cho phép kiểm tra trạng thái của FutureTask như đã hoàn tất hay đã bị hủy.
Để tạo một FutureTask, chúng ta cần một đối tượng Callable. Sau đó, có thể sử dụng Java Thread Pool Executor để xử lý các tác vụ này một cách bất đồng bộ.
Sau đây ví dụ về FutureTask qua một chương trình đơn giản. Vì FutureTask yêu cầu một đối tượng Callable, chúng ta sẽ tạo một lớp triển khai Callable đơn giản.
package com.journaldev.threads;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private long waitTime;
public MyCallable(int timeInMillis){
this.waitTime=timeInMillis;
}
@Override
public String call() throws Exception {
Thread.sleep(waitTime);
//return the thread name executing this callable task
return Thread.currentThread().getName();
}
}
Đây là một chương trình ví dụ về FutureTask, minh họa các phương thức thường được sử dụng của nó.
package com.journaldev.threads;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureTaskExample {
public static void main(String[] args) {
MyCallable callable1 = new MyCallable(1000);
MyCallable callable2 = new MyCallable(2000);
FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(futureTask1);
executor.execute(futureTask2);
while (true) {
try {
if(futureTask1.isDone() && futureTask2.isDone()){
System.out.println("Done");
//shut down executor service
executor.shutdown();
return;
}
if(!futureTask1.isDone()){
//wait indefinitely for future task to complete
System.out.println("FutureTask1 output="+futureTask1.get());
}
System.out.println("Waiting for FutureTask2 to complete");
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if(s !=null){
System.out.println("FutureTask2 output="+s);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}catch(TimeoutException e){
//do nothing
}
}
}
}
Khi chạy chương trình trên, bạn sẽ thấy nó không in ra gì ngay lập tức. Đó là vì phương thức get() của FutureTask sẽ chặn luồng hiện tại cho đến khi tác vụ hoàn thành, sau đó mới trả về đối tượng kết quả. Ngoài ra, get() còn có một phiên bản nạp chồng phương thức cho phép chỉ chờ trong một khoảng thời gian nhất định, và chúng ta đang sử dụng nó cho futureTask2. Cũng cần lưu ý việc sử dụng phương thức isDone() để đảm bảo chương trình sẽ kết thúc sau khi tất cả các tác vụ đã được thực thi.
Kết quả đầu ra của chương trình trên sẽ là:
FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=pool-1-thread-2
Done
Thoạt nhìn, FutureTask có vẻ không mang lại lợi ích gì nổi bật. Tuy nhiên, nó trở nên hữu ích khi chúng ta muốn ghi đè một vài phương thức của interface Future mà không cần phải triển khai toàn bộ các phương thức của interface này.
Cách đây không lâu, tôi đã viết một bài về các interface Callable và Future trong Java, cho phép chúng ta vừa tận dụng lợi ích xử lý song song của luồng (thread), vừa có khả năng trả về giá trị cho chương trình gọi.
FutureTask là một triển khai cụ thể cơ bản của interface Future và cung cấp khả năng xử lý bất đồng bộ. Lớp này có các phương thức để khởi chạy hoặc hủy một tác vụ, đồng thời cho phép kiểm tra trạng thái của FutureTask như đã hoàn tất hay đã bị hủy.
Để tạo một FutureTask, chúng ta cần một đối tượng Callable. Sau đó, có thể sử dụng Java Thread Pool Executor để xử lý các tác vụ này một cách bất đồng bộ.
Sau đây ví dụ về FutureTask qua một chương trình đơn giản. Vì FutureTask yêu cầu một đối tượng Callable, chúng ta sẽ tạo một lớp triển khai Callable đơn giản.
package com.journaldev.threads;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private long waitTime;
public MyCallable(int timeInMillis){
this.waitTime=timeInMillis;
}
@Override
public String call() throws Exception {
Thread.sleep(waitTime);
//return the thread name executing this callable task
return Thread.currentThread().getName();
}
}
Đây là một chương trình ví dụ về FutureTask, minh họa các phương thức thường được sử dụng của nó.
package com.journaldev.threads;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureTaskExample {
public static void main(String[] args) {
MyCallable callable1 = new MyCallable(1000);
MyCallable callable2 = new MyCallable(2000);
FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(futureTask1);
executor.execute(futureTask2);
while (true) {
try {
if(futureTask1.isDone() && futureTask2.isDone()){
System.out.println("Done");
//shut down executor service
executor.shutdown();
return;
}
if(!futureTask1.isDone()){
//wait indefinitely for future task to complete
System.out.println("FutureTask1 output="+futureTask1.get());
}
System.out.println("Waiting for FutureTask2 to complete");
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if(s !=null){
System.out.println("FutureTask2 output="+s);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}catch(TimeoutException e){
//do nothing
}
}
}
}
Khi chạy chương trình trên, bạn sẽ thấy nó không in ra gì ngay lập tức. Đó là vì phương thức get() của FutureTask sẽ chặn luồng hiện tại cho đến khi tác vụ hoàn thành, sau đó mới trả về đối tượng kết quả. Ngoài ra, get() còn có một phiên bản nạp chồng phương thức cho phép chỉ chờ trong một khoảng thời gian nhất định, và chúng ta đang sử dụng nó cho futureTask2. Cũng cần lưu ý việc sử dụng phương thức isDone() để đảm bảo chương trình sẽ kết thúc sau khi tất cả các tác vụ đã được thực thi.
Kết quả đầu ra của chương trình trên sẽ là:
FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=pool-1-thread-2
Done
Thoạt nhìn, FutureTask có vẻ không mang lại lợi ích gì nổi bật. Tuy nhiên, nó trở nên hữu ích khi chúng ta muốn ghi đè một vài phương thức của interface Future mà không cần phải triển khai toàn bộ các phương thức của interface này.