Giới thiệu
Mô hình đồng thời trong Java đã trở thành một công cụ cực kỳ quan trọng cho các lập trình viên. Trong bài viết này, chúng ta sẽ tìm hiểu về FutureTask, một trong những thành phần quan trọng trong việc thực hiện các tác vụ không đồng bộ trong Java. Nếu bạn chưa đọc hai phần trước, hãy quay lại để nắm bắt các khái niệm cơ bản về CountDownLatch và cơ chế đồng thời trong Java.
Tóm tắt về Callables & Future
FutureTask là một "tính toán không đồng bộ có thể hủy bỏ". Lớp này cung cấp một triển khai cơ bản của Future, đi kèm với các phương thức để bắt đầu và hủy bỏ một tính toán, kiểm tra xem tính toán đã hoàn tất hay chưa, và lấy kết quả của tính toán đó.
Sự khác biệt giữa Callable và Runnable
- Runnable: Không thể trả về kết quả.
- Callable: Có thể trả về kết quả của kiểu
Future.
Khi bạn gửi một tác vụ Callable đến Executor, nó sẽ không chặn và trả về ngay lập tức. Để kiểm tra xem tác vụ đã hoàn tất hay chưa, bạn có thể sử dụng phương thức isDone. Khi isDone trả về TRUE, bạn có thể truy cập kết quả từ Future bằng cách sử dụng future.get(). Tuy nhiên, hãy nhớ rằng phương thức get là một phương thức chặn, tức là nếu tác vụ chưa hoàn thành khi gọi get, nó sẽ làm tê liệt luồng.
ExecutorService
FutureTask được thiết kế để được sử dụng thông qua giao diện ExecutorService và các lớp thực hiện nó. Các lớp này sử dụng FutureTask để chia nhỏ các luồng và tạo ra các tác vụ nền không chặn. Các Executor thường quản lý một nhóm các luồng để bạn không cần phải tạo các luồng một cách thủ công. Tất cả các luồng trong một nhóm luồng có thể được tái sử dụng.
Ví dụ mã nguồn
Dưới đây là một ví dụ về cách sử dụng FutureTask trong Java:
Lớp ProductInfo
java
package com.somitsolutions.training.java.ExperimentationWithFutureTask;
public class ProductInfo {
private String productName;
private float productPrice;
public ProductInfo(String productName, float productPrice) {
this.productName = productName;
this.productPrice = productPrice;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public float getProductPrice() {
return productPrice;
}
public void setProductPrice(float productPrice) {
this.productPrice = productPrice;
}
}
Lớp Preloader
java
package com.somitsolutions.training.java.ExperimentationWithFutureTask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;
public class Preloader {
static ExecutorService executor = Executors.newFixedThreadPool(1);
List<ProductInfo> productInfo = new ArrayList<>();
private FutureTask<List<ProductInfo>> future = null;
public List<ProductInfo> get() {
try {
productInfo = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return productInfo;
}
public boolean cancel() {
return future.cancel(true);
}
public boolean isDone() {
return future.isDone();
}
public void start() {
System.out.println("Tác vụ đang được gửi...");
future = (FutureTask<List<ProductInfo>>) (executor.submit(new LoadProductInfo()));
}
private List<ProductInfo> loadProductInfo() {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100000; i++) {
ProductInfo productI = new ProductInfo(Integer.toString(i), i);
productInfo.add(productI);
}
return productInfo;
}
class LoadProductInfo implements Callable<List<ProductInfo>> {
@Override
public List<ProductInfo> call() throws Exception {
return loadProductInfo();
}
}
}
Lớp Main
java
package com.somitsolutions.training.java.ExperimentationWithFutureTask;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<ProductInfo> listOfProductInfo;
System.out.println(Thread.currentThread().getName());
Preloader preloader = new Preloader();
preloader.start();
int count = 0;
while (!preloader.isDone()) {
System.out.println("Tác vụ chưa hoàn thành...");
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Preloader.executor.shutdown();
System.exit(0);
}
if (count == 100) {
preloader.cancel();
System.out.println("Tác vụ đã bị hủy...");
Preloader.executor.shutdown();
System.exit(0);
}
}
if (!preloader.cancel() && preloader.isDone()) {
listOfProductInfo = preloader.get();
}
System.out.println(listOfProductInfo.get(0).getProductName());
System.out.print(listOfProductInfo.get(0).getProductPrice());
Preloader.executor.shutdown();
}
}
Các phương pháp tốt nhất
- Sử dụng ExecutorService: Thay vì tạo luồng bằng tay, hãy sử dụng
ExecutorServiceđể quản lý luồng, giúp tối ưu hóa hiệu suất. - Kiểm tra trạng thái: Sử dụng phương thức
isDoneđể kiểm tra tình trạng hoàn thành của tác vụ trước khi gọigetđể tránh tình trạng chặn luồng.
Cạm bẫy thường gặp
- Chặn luồng: Gọi
getkhi tác vụ chưa hoàn thành sẽ làm chặn luồng, ảnh hưởng đến hiệu suất ứng dụng. - Hủy tác vụ: Hãy chắc chắn rằng bạn kiểm tra trạng thái của tác vụ trước khi hủy, để tránh các lỗi không mong muốn.
Mẹo hiệu suất
- Tối ưu hóa thời gian chờ: Thay vì chờ đợi, hãy xem xét cách thức xử lý các tác vụ song song để cải thiện hiệu suất.
- Tái sử dụng luồng: Sử dụng các nhóm luồng để giảm thiểu chi phí tạo luồng mới.
Khắc phục sự cố
- Lỗi InterruptedException: Kiểm tra xem luồng có bị gián đoạn hay không và xử lý lỗi phù hợp.
- Kiểm tra lỗi ExecutionException: Luôn phải kiểm tra các ngoại lệ có thể xảy ra trong quá trình thực thi để xử lý các tình huống không mong muốn.
Kết luận
FutureTask là một công cụ mạnh mẽ giúp bạn thực hiện các tác vụ không đồng bộ trong Java. Bằng cách hiểu rõ cách hoạt động của nó cũng như các phương pháp tốt nhất, bạn có thể tối ưu hóa hiệu suất ứng dụng của mình. Hãy bắt đầu thử nghiệm với FutureTask ngay hôm nay để cải thiện hiệu suất ứng dụng của bạn!
Câu hỏi thường gặp
1. FutureTask có thể hủy được không?
- Có, bạn có thể hủy
FutureTasknếu nó chưa bắt đầu thực hiện.
2. Tại sao get() lại chặn luồng?
- Phương thức
getsẽ chặn luồng cho đến khi kết quả của tác vụ có sẵn, điều này cần thiết để đảm bảo rằng bạn có được dữ liệu chính xác.
3. Có cách nào để tránh chặn luồng khi lấy kết quả không?
- Bạn có thể sử dụng phương thức
isDonetrước khi gọigetđể kiểm tra trạng thái của tác vụ.
Hãy theo dõi các bài viết tiếp theo để tìm hiểu thêm về các khái niệm đồng thời trong Java!