English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Principle of Java thread pool
Two-level scheduling model of Executor framework
In the HotSpot VM model, Java threads are mapped one-to-one with local operating system threads. When a Java thread starts, it creates a local operating system thread, and when a Java thread terminates, the corresponding operating system thread is also destroyed and recycled, while the operating system schedules all threads and allocates them to available CPUs.
At the upper level, JAVA programs decompose the application into multiple tasks, and then use the application-level scheduler (Executor) to map these tasks into a fixed number of threads; at the lower level, the operating system kernel maps these threads to hardware processors.
Executor framework class diagram
In the previous introduction to JAVA threads, they are both work units and execution mechanisms. In the Executor framework, we separate the work unit from the execution mechanism. Runnable and Callable are work units (also known as tasks), and the execution mechanism is provided by Executor. In this way, Executor is based on the producer-consumer model, where the operation of submitting tasks is equivalent to the producer, and the threads executing tasks are equivalent to the consumers.
1From the class diagram, the Executor interface is the foundation of the asynchronous task execution framework, which can support various different types of task execution strategies.
public interface Executor { void execute(Runnable command); }
The Executor interface provides an execution method, where the task is of Runnable type and does not support Callable type.
2The ExecutorService interface implements the Executor interface, mainly providing methods for shutting down the thread pool and submit:
public interface ExecutorService extends Executor { List<Runnable> shutdownNow(); boolean isTerminated(); <T> Future<T> submit(Callable<T> task); }
Additionally, there are two important implementation classes of this interface: ThreadPoolExecutor and ScheduledThreadPoolExecutor.
Among them, ThreadPoolExecutor is the core implementation class of the thread pool, used to execute submitted tasks; while ScheduledThreadPoolExecutor is an implementation class that can run tasks after a given delay or execute commands periodically.
In the previous article, I used ThreadPoolExecutor to create the thread pool I needed by giving different parameters, but this method is not recommended in the later work, and it is recommended to use the Executors factory method to create the thread pool
Here is the distinction between the concepts of thread pool and thread group (ThreadGroup and ThreadPoolExecutor):
b、Thread pool is used to solve the problems of thread lifecycle overhead and resource shortages, mainly used to manage threads.
a、Thread group represents a collection of threads.
The thread pool provides a solution for the lifecycle overhead of threads and resource shortages, mainly used to manage threads.3There are three types of ThreadPoolExecutor: SingleThreadExecutor, FixedThreadExecutor, and CachedThreadPool that Executors can create
a、SingleThreadExecutor: Single-threaded thread pool
ExecutorService threadPool = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
From the source code, we can see that the creation of a single-threaded thread pool also goes through ThreadPoolExecutor, where the core thread count and thread count are both1And the work queue uses an unbounded queue. Since it is a single-threaded work, it can only handle one task at a time, so all subsequent tasks are blocked in the work queue and can only be executed one by one.
b、FixedThreadExecutor: Fixed size thread pool
ExecutorService threadPool = Executors.newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
This is similar to a single thread, but it creates a fixed number of thread sizes.
c、CachedThreadPool: Unbounded thread pool
ExecutorService threadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
An unbounded thread pool means there is no work queue, tasks are executed as they come in, and threads are created if there are not enough threads. The difference from the previous two is that idle threads will be recycled, and the idle time is60s. This is suitable for executing many short-term asynchronous small programs or servers with light load.
Detailed explanation of Callable, Future, and FutureTask
Callable and Future were introduced in the later versions of JAVA, similar to the Runnable interface, classes that implement the Callable interface and classes that implement Runnable are both tasks that can be executed by threads.
The relationship between the three:
Callable is an asynchronous computation task wrapped by Runnable
Future is used to save the result of Callable asynchronous computation
FutureTask encapsulates the entity class of Future
1、The differences between Callable and Runnable
a、the method defined by Callable is call, while the method defined by Runnable is run.
b、the call method has a return value, while the run method has no return value.
c、the call method can throw an exception, while the run method cannot throw an exception.
2、Future
Future represents the result of an asynchronous computation and provides the following methods, mainly for determining whether the task is completed, interrupting the task, and obtaining the execution result of the task
public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
3、FutureTask
Cancelable asynchronous calculation, this class provides a basic implementation of Future, and the result can only be obtained when the calculation is completed. If the calculation has not been completed, the get method will block.
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask not only implements the Future interface but also implements the Runnable interface, so it can not only be used as a task to be executed by Executor, but also to create a thread through Thread.
Callable and FutureTask
Define a callable task:
public class MyCallableTask implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("callable do somothing"); Thread.sleep(5000); return new Random().nextInt(100); } }
public class CallableTest { public static void main(String[] args) throws Exception { Callable<Integer> callable = new MyCallableTask(); FutureTask<Integer> future = new FutureTask<Integer>(callable); Thread thread = new Thread(future); thread.start(); Thread.sleep(100); //Attempt to cancel the execution of this task future.cancel(true); //Determine whether the task is cancelled before it is normally completed System.out.println("future is cancel:" + future.isCancelled()); if(!future.isCancelled()) { System.out.println("future is cancelled"); } //Determine whether the task is completed System.out.println("future is done:" + future.isDone()); if(!future.isDone()) { System.out.println("future get=" + future.get()); } else { //The task is completed System.out.println("task is done"); } } }
Execution result:
callable do somothing future is cancel:true future is done:true task is done
This DEMO mainly demonstrates the state transition by calling the method to set the state of FutureTask.
a、in the11line, try to cancel the execution of the task, this method returns false if the task is already completed or canceled, and returns true if it can cancel the task that has not been completed yet. In this DEMO, since the task is still in the sleep state, it can be canceled successfully.
future.cancel(true);
b、in the13line, determine whether the task cancellation is successful: if the task is canceled before it is normally completed, return true
System.out.println("future is cancel:" + future.isCancelled());
c、in the19line, determine whether the task is completed: if the task is completed, return true, the following situations all belong to task completion: normal termination, exception, or completion due to cancellation.
In our DEMO, the task is completed due to cancellation.
System.out.println("future is done:" + future.isDone());
d、in the22line, get the result of the asynchronous thread execution, in this DEMO, I have not reached here. It should be noted that the future.get method will block the current thread until the task is completed and the result is returned.
System.out.println("future get=" + future.get());
Callable and Future
public class CallableThread implements Callable<String> { @Override public String call() throws Exception { System.out.println("Enter Call method, start sleeping, sleep time is:" + System.currentTimeMillis()); Thread.sleep(10000); return "power outage today"; } public static void main(String[] args) throws Exception { ExecutorService es = Executors.newSingleThreadExecutor(); Callable<String> call = new CallableThread(); Future<String> fu = es.submit(call); es.shutdown(); Thread.sleep(5000); System.out.println("Main thread is sleeping"5seconds, the current time" + System.currentTimeMillis()); String str = fu.get(); System.out.println("Future has obtained the data, str=" + str + "; The current time is:" + System.currentTimeMillis()); } }
Execution result:
Enter the Call method, start sleeping, the sleep time is:1478606602676 the main thread is sleeping5seconds, the current time1478606608676 Future has obtained the data, str=power outage today; the current time is:1478606612677
Here, the future is directly thrown into the thread pool for execution. Since the execution result needs to be printed, from the perspective of the main thread, although it is sleeping5s, but the time difference between the execution of the Call method and obtaining the task result is exactly10s, it indicates that the get method will block the current thread until the task is completed.
The same effect can also be achieved through FutureTask:
public static void main(String[] args) throws Exception { ExecutorService es = Executors.newSingleThreadExecutor(); Callable<String> call = new CallableThread(); FutureTask<String> task = new FutureTask<String>(call); es.submit(task); es.shutdown(); Thread.sleep(5000); System.out.println("The main thread waits for5seconds, the current time is:" + System.currentTimeMillis()); String str = task.get(); System.out.println("Future has obtained the data, str=" + str + "; The current time is:" + System.currentTimeMillis()); }
The above combination can bring us some changes such as:
In a certain scenario, if method A returns a data that needs10s, the code after method A needs to run20s, but this20s during the execution process, only the following10s depends on the result of method A. If the same synchronous method is used as before, it is inevitable that there will be10s time is wasted, if the previous two combinations are used, the efficiency will improve:
1, put the content of method A into the call() method of the Callable implementation class first
2, execute A task in the main thread through the thread pool
3, execute the following method10, code that does not depend on the result of method A's execution
4, get the result of method A's execution, and execute the following method10Code that depends on the result of method A's execution
This code execution efficiency is improved all at once, and the program does not need to be stuck at method A.
Thank you for reading, I hope it can help everyone. Thank you for your support of this site!