Code ví dụ Callable, Future, Executors trong Java

Code ví dụ Callable, Future, Executors trong Java.

(Xem thêm: So sánh Future và CompletableFuture trong Java)

Thay vì tạo thread bằng việc Extend Thread hoặc implement Runable và tự quản lý số lượng thread. Thì ta có một hướng tiếp cận khác đó là sử dụng Callable và Future. (Cho phép hủy các thread, kiểm tra thread đã hoàn thành chưa, quản lý số thread chạy cùng lúc …)

Callable, Future, Executors là gì?

  • Callablelà một interface trong java, nó định nghĩa một công việc và trả về một kết quả trong tương lai và có thể throw Exception
  • Future là kết quả trả về của Callable, nó thể hiện kết quả của một phép tính không đồng bộ, cho phép kiểm tra trạng thái của phép tính (đã thực hiện xong chưa, kết quả trả về là gì…)
  • Executors là một class tiện ích trong Java, dùng để tạo thread pool, đối tượng Callable cho các xử lý bất đồng bộ.

Code ví dụ Callable, Future, Executors trong Java.

Ví dụ mình muốn thực hiện nhiều phép tính tổng 2 số nguyên cùng lúc:

Đầu tiên mình tạo một class thực hiện implement Callable với kiểu trả về là Integer và implement phương thức tính tổng

package stackjava.com.futureTutorial;

import java.util.concurrent.Callable;

public class Calculator implements Callable<Integer> {

  private int a;
  private int b;

  public Calculator(int a, int b) {
    this.a = a;
    this.b = b;
  }
  
	public int sum() {
		int sum = this.a + this.b;
		System.out.println("result: " + a + " + " + b + " = " + sum);
		return sum;
	}

  @Override
  public Integer call() throws Exception {
    return this.sum();
  }

}

Ví dụ 1: Bây giờ mình sử dụng Executors để tạo một thread pool chứa các các đối tượng Calculator

package stackjava.com.futureTutorial;

import java.util.concurrent.*;

public class Demo1 {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
    Calculator c1 = new Calculator(1, 2);
    Calculator c2 = new Calculator(1, 3);
    Calculator c3 = new Calculator(2, 3);

    ExecutorService executor = Executors.newFixedThreadPool(10);
    Future<Integer> f1 = executor.submit(c1);
    Future<Integer> f2 = executor.submit(c2);
    Future<Integer> f3 = executor.submit(c3);
    System.out.println("Done");
    
    executor.shutdown();
  }
}
  • Mỗi đối tượng Calculator có thể hiểu là một thread, khi nó được submit vào ExecutorService thì nó sẽ được thực thi.
  • Executors.newFixedThreadPool(10); tức là tạo ra một thread pool chứa tối đa 10 thread chạy cùng lúc.
  • executor.shutdown();: Thực hiện tắt executor khi không còn task (đối tượng  Callable) nào ở bên trong (các task đã hoàn thành). Nếu bạn không có lệnh này thì chương trình của bạn sẽ chạy mãi vì nó luôn có một thread kiểm tra task trong executor để thực thi.
  • Đối tượng Future sẽ chứa kết quả phép tính tổng

Chạy ví dụ trên ta có kết quả sau:

Done
result: 2 + 3 = 5
result: 1 + 2 = 3
result: 1 + 3 = 4

Kết quả hiển thị không theo thứ tự được submit vào executor vì nó chạy cùng lúc.

Ví dụ 2: Bây giờ mình muốn in ra kết quả của c1 thì mình sẽ gọi f1.get()

package stackjava.com.futureTutorial;

import java.util.concurrent.*;

public class Demo2 {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
    Calculator c1 = new Calculator(1, 2);
    Calculator c2 = new Calculator(1, 3);
    Calculator c3 = new Calculator(2, 3);

    ExecutorService executor = Executors.newFixedThreadPool(10);
    Future<Integer> f1 = executor.submit(c1);
    System.out.println(f1.get());
    Future<Integer> f2 = executor.submit(c2);
    Future<Integer> f3 = executor.submit(c3);
    System.out.println("Done");
    
    executor.shutdown();
  }
}

Kết quả:

result: 1 + 2 = 3
3
Done
result: 1 + 3 = 4
result: 2 + 3 = 5

Khi bạn gọi f1.get() thì nó sẽ block thread chính lại để khi nào đối tượng c1 thực hiện xong và trả về kết quả.

Trường hợp đối tượng c1 mất quá nhiều thời gian để tính tổng  2 số thì cả chương trình sẽ bị delay rất lâu. Giải pháp cho trường hợp này là sử dụng method get() với thời gian timeout:

Ví dụ nếu sau 1 giây mà chưa có kết quả thì không chờ nữa: f1.get(1, TimeUnit.SECONDS)

Một số hạn chế của Future

  • Future không thông báo khi nó hoàn thành: (không biết được khi nào hoàn thành): ví dụ mình muốn sau khi f1 hoàn thành thì làm gì đó nhưng Future không hỗ trợ bắt được sự kiện hoặc xử lý callback khi f1 hoàn thành
  • Không thể thực hiện xử lý chờ hoặc xử lý theo thứ tự các future: ví dụ mình muốn f1 và f2 hoàn thành xong thì mới thực hiện f3 hoặc thực hiện theo thứ tự f1, f2, f3 nhưng Future không có cách nào xử lý được việc này.

Nếu muốn xử lý các vấn đề trên ta lại phải dùng while hoặc tạo 1 thread riêng và sử dụng method f1.isDone() để check khi nào future hoàn thành…

Từ Java 8 ta có thêm CompleteFuture để khắc phục những hạn chế bên trên. (Mình sẽ làm ví dụ CompleteFuture trong bài sau)

 

Okay, Done!

Download code ví dụ trên tại đây.

 

References:

https://dzone.com/articles/java-callable-future-understanding

stackjava.com