Executor, ExecutorService, Executors, Callable, Future and FutureTask of Java Multithreading

Original address: https://www.cnblogs.com/zhrb/p/6372799.html

1. Introduction

Beginners learn Java multithreading, often use Threadand Runnablecreate and start threads. For example:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
t1.start();

We need to create and start the Thread object ourselves.

Important concepts:

  1. The implementing Runnableclass should be seen as a task, not a thread. In Java multithreading, we must have a clear understanding that tasks and threads are different concepts. You can use a thread (Thread) to perform tasks (such as Runnable), but a task is not a thread.
  2. There are two different types of tasks in Java multithreading, Runnable type tasks (without return value) and Callable type tasks (with return value).

2. Use Executor to execute threads

Some existing executors can help us manage Thread objects. You don't need to create and control Thread objects yourself. For example, you don't have to write in code new Threador thread1.start()you can use multithreading as well. For example:

ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {//5个任务
    exec.submit(new Runnable() {
        @Override
        public void run() {            
            System.out.println(Thread.currentThread().getName()+" doing task");
         }
     });
}
exec.shutdown();  //关闭线程池

The output is as follows:

pool-1-thread-2 doing task
pool-1-thread-1 doing task
pool-1-thread-3 doing task
pool-1-thread-4 doing task
pool-1-thread-5 doing task

From the output we can see that exec uses 5 threads in thread pool 1 to do these tasks.

In this example, the Executor is responsible for managing tasks. The so-called tasks here are anonymous inner classes that implement the Runnable interface. As for how many threads to use, when to start these threads, and whether to use a thread pool or a single thread to complete these tasks, we don't need to worry about it. It is completely responsible for the executor of exec. Here exec(newCachedThreadPool) points to a thread pool that can create new threads on demand.

Executors are equivalent to the factory class of executors, including factory methods for various common executors, and can directly create common executors. Several commonly used actuators are as follows:

Executors.newCachedThreadPool, which can create a thread pool of new threads as needed. Threads once created in the thread pool may be used to complete another task after completing a task.

Executors.newFixedThreadPool(int nThreads), Create a thread pool with a fixed number of reusable threads. This thread pool contains at most nThread threads.

Executors.newSingleThreadExecutor(), creates an Executor that uses a single worker thread. Even if there are more tasks, only 1 thread is used to complete the task.

Executors.newSingleThreadScheduledExecutor(), creates a single-threaded executor that can schedule commands to run after a given delay or to execute periodically .

An example of newSingleThreadExecutor is as follows:

ExecutorService exec = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
    exec.execute(new Runnable() {//execute方法接收Runnable对象,无返回值
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });
}
exec.shutdown();

The output is as follows:

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

It can be seen that although there are 5 tasks (5 new Runnables), they are completed by only 1 thread.

Best Practice: We should use an existing Executor or ExecutorService implementation class. For example, the newCachedThreadPool mentioned above can use the thread pool to help us reduce overhead (creating a new thread has a certain cost), and newFixedThreadPool can limit the number of concurrent threads. That is, we generally use the factory methods of Executors to create the executors we need.

Common methods of Executor and ExecutorService

execute method:

The Executor interface has only void execute(Runnable command)methods. From the method declaration, we can see that the input parameter is an object of type Runnable. Common examples are as follows:

Executor executor = anExecutor;
executor.execute(new RunnableTask1());

But how to execute it and whether to call the thread to execute is determined by the corresponding Executor interface implementation class. For example, the previous newCachedThreadPoolthread pool is used for execution. Executor decouples task submission from how each task is run (how threads are used, scheduling).

submit method:

ExecutorServiceThe interface inherits from the Executor interface and extends the execute method in the parent interface. There are two commonly used submit methods

Future<?> submit(Runnable task) 
<T> Future<T> submit(Callable<T> task)

It can be seen that one of these two common methods receives Runnable type input parameters, and the other receives Callable type input parameters. The Callable parameter allows the task to return a value, while the Runnable has no return value. That is to say, if we want the thread to have a return result, we should use the Callable type parameter.

invokeAll and invokeAny methods:

Batch execute a set of Callable tasks. WhereinvokeAll is to return a list of Futures representing the results after all tasks are completed. And invokeAny is to wait for any one of the tasks in this batch of tasks to complete and then return. From the return results of the two methods, we can also see the difference between the two methods:

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> T invokeAny(Collection<? extends Callable<T>> tasks)

What invokeAll returns is a List , and what is returned by invoke is T.

shutdown() method:

Initiate a sequential shutdown, executing previously submitted tasks, but not accepting new tasks. After executing this method, the thread pool waits for the end of the task and closes, and no longer receives new tasks. If the method is executed shutdown()after the method is executed, the RejectedExecutionExceptionexecute will be thrown directly . Don't ask me why I know... just crawled out of the pit.

Principle: As long as the ExecutorService (thread pool) is no longer used, it should be closed to recycle resources. Note that this is no longer used .

There are many methods mentioned above, which can be understood with the following examples. You can remember the execute method and the shutdown method first.

3. Using Callable and Future

Callable interface

RunnableThe methods in the interface have public void run()no return value. If we want the thread to return the result after the operation, there is nothing we can do with Runnable. This time we should use Callable. Callable represents a task with a return value. A class that implements the Callable interface looks like this:

class CalcTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName();
    }
}

This task is relatively simple, is to return the name of the current thread. Compared with Runnable, there is a return value, where the return value type is String, and it can also be other types.

Use the following code to call:

ExecutorService exec = Executors.newCachedThreadPool();
List<Callable<String>> taskList = new ArrayList<Callable<String>>();
/* 往任务列表中添加5个任务 */
for (int i = 0; i < 5; i++) {
    taskList.add(new CalcTask());
}
/* 结果列表:存放任务完成返回的值 */
List<Future<String>> resultList = new ArrayList<Future<String>>();
try {
    /*invokeAll批量运行所有任务, submit提交单个任务*/
    resultList = exec.invokeAll(taskList);
} catch (InterruptedException e) {
    e.printStackTrace();
}
try {
    /*从future中输出每个任务的返回值*/
    for (Future<String> future : resultList) {
        System.out.println(future.get());//get方法会阻塞直到结果返回
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

The output is as follows:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5

Future interface

In the above example we used the Future interface. Future represents the result of an asynchronous computation . It provides methods to check whether the calculation is complete, to wait for the completion of the calculation, and get the result of the calculation. In the above example, the exec executor executes a list of tasks of type Callable and then gets the result list of type Futuer resultList.

get method

Wait for the calculation to complete, then get its result.

isDone method

It is used to query whether the task is completed. The example is as follows:

/*新建一个Callable任务*/
Callable<Integer> callableTask = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        System.out.println("Calculating 1+1!");
        TimeUnit.SECONDS.sleep(2);//休眠2秒
        return 2;
    }
}; 
ExecutorService executor = Executors.newCachedThreadPool();
Future<Integer> result = executor.submit(callableTask);
executor.shutdown();
while(!result.isDone()){//isDone()方法可以查询子线程是否做完
    System.out.println("子线程正在执行");
    TimeUnit.SECONDS.sleep(1);//休眠1秒
}
try {
    System.out.println("子线程执行结果:"+result.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

The output is as follows:

Calculating 1+1!
子线程正在执行
子线程正在执行
子线程执行结果:2

4.FutureTask

FutureTaskA class is an implementation of the Future interface. The FutureTask class implements the RunnableFuture interface, and RunnableFuture inherits the Runnable interface and the Future interface, so:

  1. FutureTask can be executed by thread as Runnable
  2. The return value of the incoming Callable object can be obtained as a Future.
    Examples are as follows:

    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
       @Override
       public Integer call() throws Exception {
        System.out.println("futureTask is wokring 1+1!");
        return 2;
       }
    });
    Thread t1 = new Thread(futureTask);//1.可以作为Runnable类型对象使用
    t1.start();
    try {
       System.out.println(futureTask.get());//2.可以作为Future类型对象得到线程运算返回值
    } catch (ExecutionException e) {
       e.printStackTrace();
    }

    The output is as follows:

    futureTask is wokring 1+1!
    2

    It can be seen that FutureTask can be used as a Runnable task with a return value.

Analysis:FutureTask<Integer> futureTask = new FutureTask<>(new Callable...) Equivalent to converting the Callable task into a Runnable task, you can use threads to execute the task. It is futureTask.get()equivalent to converting Callable to Future, so as to obtain the result of asynchronous operation.

In addition to receiving input parameters of Runnable and Callable types, ExecutorService executors can also receive FutureTask types. Examples are as follows:

FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        System.out.println("futureTask is wokring 1+1!");
        TimeUnit.SECONDS.sleep(2);
        return 2;
    }
});
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(futureTask);//也可以使用execute,证明其是一个Runnable类型对象
executor.shutdown();
while(!futureTask.isDone()){
    System.out.println("子线程还没做完,我再睡会");
    TimeUnit.SECONDS.sleep(1);
}
try {
    System.out.println("子线程运行的结果:"+futureTask.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325570423&siteId=291194637