Callable、Runnable、Future 和 FutureTask

Callableand Futureare introduced in subsequent versions of Java, Callablesimilar to Runnablethe interface, both classes that implement Callbackthe interface and Runnableclasses that implement the interface can be used for tasks executed by threads.

The following are the relevant source codes of the two interfaces:

// /xref/libcore/ojluni/src/main/java/java/util/concurrent/Callable.java
public interface Callable<V> {
    
    
    V call() throws Exception;
}

// /xref/libcore/ojluni/src/main/java/java/lang/Runnable.java
public interface Runnable {
    
    
    public abstract void run();
}

It can be seen from the above source code: Callable.call()the method has a return value and can throw an exception; Runable.run()the method has no return value and will not throw an exception.

CallableThe interface can be seen as Runnablea supplement to the interface, and Runnablethe difference with is that it returns the result of executing the task and can throw an exception, which is generally ThreadPoolExecutorused in conjunction.

Futureis an interface that can cancel Runnableor task, determine whether the task is canceled, query whether the task is completed, and obtain the task result. CallableThe following is Futurethe relevant source code:

public interface Future<V> {
    
    

  	// 取消任务的执行。任务已经完成或者已经被取消时调用此方法,会返回 false
    boolean cancel(boolean mayInterruptIfRunning);

  	// 判断任务是否被取消
    boolean isCancelled();

  	// 判断任务是否完成。正常完成、异常以及被取消,都将返回 true
    boolean isDone();

  	// 阻塞地获取任务的执行结果
    V get() throws InterruptedException, ExecutionException;

  	// 在一定时间内,阻塞地获取任务的执行结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

But since Futureis just an interface, in order to use the functions in it, you must use its indirect implementation class FutureTask, which represents the result of asynchronous calculation.

FutureTaskis RunnableFuturethe implementation class of the interface, and RunnableFuturethe interface implements Runnablethe and Futureinterfaces. Therefore, FutureTaskit can be handed over Threador Executorexecuted.

Here is FutureTaskthe inheritance relationship for :

Inheritance relationship of FutureTask

According to FutureTask.run()the timing when the method is executed, FutureTaskit can be in the following three states:

  • Unstarted: FutureTask.run()before the method is executed, it is in the unstarted state;
  • Started: The method is in the started state FutureTask.run()during execution ;FutureTask
  • Completed: FutureTask.run()After the method execution is completed, it ends normally, or it is executed FutureTask.cacel(boolean), canceled, or executed FutureTask.run() 方法时抛出异常而结束, it is in the completed state;

The following is FutureTaska state transition diagram for :

The state of the FutureTask

For FutureTask.get()/FutureTask.get(long, TimeUnit)the method:

  • When FutureTaskis in the unstarted or started state, executing FutureTask.get()/FutureTask.get(long, TimeUnit)the method will cause the thread to block;
  • When FutureTaskis in the completed state, executing FutureTask.get()/FutureTask.get(long, TimeUnit)the method will cause the calling thread to return the result immediately or throw an exception;

For FutureTask.cancel(boolean)the method:

  • When FutureTaskis not started, executing FutureTask.cancel(boolean)the method will cause this task to never be executed;
  • When FutureTaskis in the started state, the execute FutureTask.cancel(true)method will try to stop the task by interrupting the thread executing the task;
  • When FutureTaskin the started state, the execution FutureTask.cancel(false)method will not affect the thread of the task being executed (let the task being executed run to completion);
  • When FutureTaskthe is in a completed state, the execute FutureTask.cancel(boolean)method will return false;

Schematic diagram of get and cancel execution of FutureTask

You can hand FutureTaskover to Executorfor execution, or you can <T> Future<T> ExecutorService.submit(Runnable, T)/Future<?> ExecutorService.submit(Runnable)return an Futureobject through the method, but since Futureis an interface, FutureTaskthe object is generally returned here. Then you can call FutureTask.get()/get(long, TimeUnit)the method or FutureTask.cancel(boolean)method.

It can be used when a thread needs to wait for another thread to complete a task before it can continue FutureTask. Suppose there are multiple threads performing several tasks. When multiple threads try to execute the same task at the same time, only one thread is allowed to execute the task, and other threads need to wait for the task to complete before continuing to execute.

Here is FutureTaskthe code for :

private final ConcurrentHashMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();

    private String executionTask(final String taskName) {
    
    
        while (true) {
    
    
            Future<String> future = taskCache.get(taskName); // 1.1 2.1
            if (future == null) {
    
    
                Callable<String> task = new Callable<String>() {
    
    
                    @Override
                    public String call() throws Exception {
    
    
                        return taskName;
                    }
                };
                FutureTask<String> futureTask = new FutureTask<>(task);
                future = taskCache.putIfAbsent(taskName, futureTask); // 1.3
                if (future == null) {
    
    
                    future = futureTask;
                    futureTask.run(); // 1.4 执行任务
                }
            }
            try {
    
    
                return future.get(); // 1.5 2.2 线程在此等待任务执行完成
            } catch (ExecutionException e) {
    
    
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }

The following is a schematic diagram of the code:

code execution process

When two threads try to execute the same task at the same time, if Thread 1 executes 1.3 and Thread 2 executes 2.1, then Thread 2 will wait at 2.2 until Thread 1 executes 1.4 before Thread 2 can return from 2.2.

Here's FutureTaskhow to use:

public class Test {
    
    

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        Thread.sleep(100);
        futureTask.cancel(true); // 1
        System.out.println("future is cancel: " + futureTask.isCancelled());
        if (!futureTask.isCancelled()) {
    
    
            System.out.println("future is cancelled");
        }
        System.out.println("future is done: " + futureTask.isDone());
        if (!futureTask.isDone()) {
    
    
            System.out.println("future get = " + futureTask.get()); // 2
        } else {
    
    
            System.out.println("task is done");
        }
    }
}


class MyCallable implements Callable<String> {
    
    

    @Override
    public String call() throws Exception {
    
    
        System.out.println(Thread.currentThread().getName() + "-call is running");
        Thread.sleep(5000);
        return "Hello World";
    }
}

The following is the execution result:

Results of the

Try to cancel the execution of the task at Note 1. If the method returns when the task is completed or canceled false, if it can cancel the task that has not been completed, return true, because in the above code because the task is still in a dormant state, it can be Cancel the task. At Note 2, FutureTask.get()the method will block the current thread until the execution of the task is completed and the result is returned.

For use with Callableand :Future

public class Test {
    
    

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<String> call = new MyCallable();
        Future<String> future = executorService.submit(call);
        executorService.shutdown();
        Thread.sleep(5000);
        System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
        String str = future.get();
        System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
    }
}

class MyCallable implements Callable<String> {
    
    

    @Override
    public String call() throws Exception {
    
    
        System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
        Thread.sleep(10000);
        return "Hello World";
    }
}

Results of the:

Results of the

In the above code, futureit is directly put into the thread pool for execution. Judging from the execution results, although the main thread sleeps for 5s, but call()the result of the task is obtained from the method execution, the time difference between them is 10s, indicating thatFutureTask.get() 方法会阻塞当前线程直到任务完成。

ExecutorService.shutdow()method, the thread pool is in SHUTDOWNthe state. At this time, the thread pool cannot receive new tasks, and it will wait for all tasks to be executed. If ExecutorService.shutdownNow()the method is called, the thread is in STOPthe state, and the thread pool cannot accept new tasks at this time, and will try to terminate the result being executed.

FutureTaskThe same effect can also be achieved by :

public class Test {
    
    

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<String> call = new MyCallable();
        FutureTask<String> task = new FutureTask<>(call);
        executorService.submit(task);
        executorService.shutdown();
        Thread.sleep(5000);
        System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
        String str = task.get();
        System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
    }
}

class MyCallable implements Callable<String> {
    
    

    @Override
    public String call() throws Exception {
    
    
        System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
        Thread.sleep(10000);
        return "Hello World";
    }
}

Results of the:

Results of the

The above combination brings the following changes: If there is a scenario, it takes 10s for method A to return a piece of data, and the code behind method A takes 20s to run, but in the execution result of 20s, only the latter 10s depends on the execution result of method A . If the same method is used as in the past, 10s of time will inevitably be wasted. If the first two combinations are used, the efficiency will increase:

  1. First put the content of method A into the method Callableof the implementation class call();
  2. Execute task A through the thread pool in the main thread;
  3. Execute the code in the following method for 10s that does not depend on the running result of method A
  4. Obtain the running result of method A, and execute the code that depends on the running result of method A for 10 seconds in the following method;

In this way, the efficiency of the code is improved, and the program does not need to be stuck at the A method.

reference

This article is enough to understand the relationship between Runnable, Future, Callable, and FutureTask

Guess you like

Origin blog.csdn.net/xingyu19911016/article/details/130193410