Multi-threading in one pot (Runnable, Callable, Thread, Future, FutureTask)

Multi-threading in one pot (Thread, Runnable, Callable, Future, FutureTask)

In the multi-thread execution cycle, the basic process can be briefly divided into (new execution task->create task process->task assembly process->start/execute thread->get result/status). The processes responsible for various elements are as follows Show:

Element name/process New execution task Create task process Task assembly process Start/execute thread Get results/status
Runnable interface ?
Callable interface ?
Thread class ? ? ? ? ?
Future interface ?
FutureTask class ? ?
ExecutorService interface ? ? ?

Runnable interface and Callable interface

  • The Runnable interface declares only one run() method:
public interface Runnable {<!-- -->
    public abstract void run();
}
// Create new Runnable and edit tasks
Runnable r = new Runnable() {<!-- -->
    @Override
    public void run() {<!-- -->
        System.out.println("Task Edit");
    }
};

Since the return value of the run() method is of void type, no results can be returned after the task is executed.

  • The Callable interface declares only one call() method:
public interface Callable<V> {<!-- -->
    V call() throws Exception;
}
//Create new Callable and editing tasks (return String)
Callable<String> c = new Callable<String>() {<!-- -->
    @Override
    public String call() throws Exception {<!-- -->
        System.out.println("Task Edit");
        return "return result";
    }
};

This is a generic interface, and the type returned by the call() function is the V type passed in.

Thread class (Runnable implementation class)

The Thread class implements the Runnable interface and achieves the entire process of **(new task -> new thread -> start thread -> control thread).

  • Attributes:
public
class Thread implements Runnable {<!-- -->
    /* Represents the name of Thread. The thread name can be specified through the parameters in the constructor of the Thread class */
    private volatile String name;
    /* Indicates the priority of Thread (the maximum value is 10, the minimum value is 1, and the default value is 5 */
    private int priority;
    /* daemon indicates whether Thread is a daemon thread */
    private boolean daemon = false;
    /* Represents the task to be performed by Thread */
    private Runnable target;
}
  • method:
Thread thread = new MyThread(); // Create a new thread class, rewrite run or assemble Runnable/Callable
thread.start();/* start() is used to start a thread */
thread.run();/* Inheriting the Thread class must override the run method to define the tasks to be performed. It does not require the user to call it. After the start method starts a thread, when the thread obtains CPU execution time, it enters the run() method. body to perform specific tasks. */
thread.sleep(long millis);/* Let the CPU execute other threads, but will not release the lock */
thread.yield();/* Let the CPU execute other threads without releasing the lock. But it does not control the specific time to hand over the CPU */
thread.join();/* Call the join() method in the main thread. The main thread will wait for the thread to complete execution or wait for a certain period of time. */
thread.interrupt();/* The interrupt method causes the thread to throw an exception, which can be used to interrupt a thread */

Future (interface for obtaining process status/results)

The Future interface is a further encapsulation of Runnable/Callable It can cancel the execution results of Runnable/Callable tasks, query whether they are completed, and obtain the results. The execution result can be obtained through the get method, but this method will block until the task returns the result.

public interface Future<V> {<!-- -->
    /* Cancel the task. If the cancellation is successful, it will return true, if it fails, it will return false. mayInterruptIfRunning indicates whether it is allowed to cancel the task that is being executed but has not been completed */
    boolean cancel(boolean mayInterruptIfRunning);
    
    /* Indicates whether the task was successfully canceled. If the task is successfully canceled before the task is completed normally, it returns true */
    boolean isCancelled();
    
    /* Indicates whether the task has been completed. If the task is completed, it returns true */
    boolean isDone();
    
    /* Used to obtain execution results, this method will cause blocking! ! ! Will wait until the task is completed before returning */
    V get() throws InterruptedException, ExecutionException;
    
    /* Used to obtain execution results. If the results are not obtained within the specified time, null will be returned directly */
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Because Future is just an interface, it cannot be used directly to create objects, so there is the following FutureTask.

FutureTask class (Future implementation class)

FutureTask implements the RunnableFuture interface (Runnable + Future). Compared with Thread, which is also an implementation class, it only achieves **(control process status + obtain results/status)**.

// FutureTask class (Future implementation class)
public class FutureTask<V> implements RunnableFuture<V>{<!-- -->
// code
}

// RunnableFuture interface
public interface RunnableFuture<V> extends Runnable, Future<V> {<!-- -->
    void run();
}

ExecutorService (thread pool interface)

ExecutorService provides four thread pools through the Executors factory class:

Executors factory class Function description
Executors.newCachedThreadPool() Create a cacheable thread pool. If the length of the thread pool exceeds processing needs, idle threads can be flexibly recycled. If there is no recycling, a new thread will be created. (The maximum number of concurrent threads is uncontrollable)
Executors.newFixedThreadPool(int nThreads) Create a fixed-size (nThreads) thread pool to control threads Maximum number of concurrencies, excess threads will wait in the queue
Executors.newScheduledThreadPool() Create a scheduled thread pool to support scheduled and periodic tasks Execution
Executors.newSingleThreadExecutor() Create a single-threaded thread pool, which will only use the only worker thread to execute tasks, ensuring that all Tasks are executed in the specified order (FIFO, LIFO, priority)

CachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();

CachedThreadPool has no core threads and no upper limit on the number of non-core threads, that is, all outsourcing is used, but each outsourcing is only idle for 60 seconds. After that, it will be recycled. The thread pool execution process is as follows:

  1. Submit tasks to SynchronousQueue
  2. If there is an idle thread, take out the task and execute it; if there is no idle thread, create a new one
  3. Threads that have completed the task have a survival time of 60 seconds, and threads that are idle for 60 seconds will be terminated.

CachedThreadPool is used to execute a large number of short-term small tasks concurrently, or for servers with light loads.

FixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(2);

The number of core threads and the maximum number of threads are both specified values. That is to say, when the number of threads in the thread pool exceeds the number of core threads, tasks will be placed in the blocking queue. The thread pool execution process is as follows:

  1. When the number of threads is less than the number of core threads, that is, when the number of threads is set, a new thread is created to perform the task.
  2. After the number of threads equals the number of core threads, add the task to the blocking queue
  3. Since the queue capacity is very large, you can keep adding
  4. The thread that has completed the task repeatedly goes to the queue to get the task for execution.

FixedThreadPool is used for heavily loaded servers. In order to make reasonable use of resources, it is necessary to limit the current number of threads.

SingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();

Equivalent to a special FixedThreadPool, the thread pool execution process is as follows:

  1. When there are no threads in the thread pool, create a new thread to perform the task
  2. After there is a thread, add the task to the blocking queue and keep adding
  3. The only thread keeps fetching tasks from the queue for execution

SingleThreadExecutor is used in scenarios where tasks are executed serially. Each task must be executed in sequence and does not need to be executed concurrently.

ScheduledThreadPoolExecutor

ExecutorService executorService = Executors.newScheduledThreadPool(2);

The execution process is as follows:

  1. add a task
  2. Threads in the thread pool take tasks from DelayQueue
  3. The thread obtains tasks from DelayQueue whose time is greater than or equal to the current time.
  4. After execution, modify the time of the task to the next time it is executed, and then put the task back into the queue.

ScheduledThreadPoolExecutor is used in scenarios where multiple background threads need to perform periodic tasks and the number of threads needs to be limited.

ExecutorService method to submit tasks

ExecutorService provides two methods for submitting tasks:

  • execute(): Submit a task that does not require a return value
  • submit(): Submit a task that requires a return value
execute
void execute(Runnable command);

The parameter of execute() is a Runnable, so there is no return value. After submission, it is impossible to determine whether the task was successfully executed by the thread pool. example:

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {<!-- -->
    @Override
    public void run() {<!-- -->
        // code
    }
});
submit
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

The parameters of submit() can be Callable or Runnable. After execution, a Funture object is returned, through which we can determine whether the task was executed successfully. To obtain the execution result, call the Future.get() method, which blocks the current thread until the task is completed. When submitting a Callable task, you need to use FutureTask to wrap it up:

FutureTask futureTask = new FutureTask(new Callable<String>() {<!-- --> //Create a Callable task
    @Override
    public String call() throws Exception {<!-- -->
        return "result";
    }
});
Future<?> submit = executor.submit(futureTask); // Submit to thread pool
try {<!-- -->
    Object result = submit.get(); // Get the result
} catch (InterruptedException e) {<!-- -->
    e.printStackTrace();
} catch (ExecutionException e) {<!-- -->
    e.printStackTrace();
}

Process and Method

First provide a task to be used:

Callable<String> c = new Callable<String>() {<!-- -->
    @Override
    public String call() throws Exception {<!-- -->
        return "test";
    }
};

For common multi-threaded processes, the following three methods are provided:

//The first method
FutureTask<String> ft = new FutureTask<>(c);
new Thread(ft).start();
System.out.println("ft = " + ft.get());

// The second method
FutureTask<String> ft = new FutureTask<>(c);
ExecutorService es = Executors.newCachedThreadPool();
es.submit(ft);
System.out.println("ft = " + ft.get());

//Third method
ExecutorService es = Executors.newCachedThreadPool();
Future<String> ft = es.submit(c);
System.out.println("ft = " + ft.get());