0
0
Lập trình
Admin Team
Admin Teamtechmely

Mô hình đồng thời trong Java - Phần III: FutureTask

Đăng vào 8 tháng trước

• 7 phút đọc

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 Copy
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 Copy
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 Copy
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ọi get để tránh tình trạng chặn luồng.

Cạm bẫy thường gặp

  • Chặn luồng: Gọi get khi 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 FutureTask nế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 get sẽ 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 isDone trước khi gọi get để 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!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào