Two ways to create threads are explained in Java Concurrency (Fundamentals)-creating, running, and stopping a thread: Directly inheriting the thread class and implementing the Runnable interface and assigning it to thread, which is no problem when the threads are created in less time. However, when you need to create a large number of threads, there will be problems, because this method of using the thread creation statements randomly scattered in the code, unable to manage the thread uniformly, we will not be able to manage the number of threads created, and excessive thread creation will directly crash the system.
From a high-poly perspective, we should create a unified creation and run interface for us to manage these threads, and this unified create and run interface is the executor framework for JDK 5.
Executor frame
In the Java class Library, the primary abstraction for task execution is not thread, but executor, which is defined as follows:
Public interface Executor { void execute (Runnable command);}
Although executor is a simple interface, it provides the foundation for a flexible and powerful asynchronous task execution framework that supports a variety of different types of task execution policies, providing a standard way to decouple the submission process from the execution process.
Executor based on the producer-consumer model, the actions that are submitted to the task are equivalent to the producer (generating the unit of work to be completed) and the thread executing the task is the consumer (executing the work unit). The simplest way to implement a producer-consumer model in a program is to use executor.
The executor interface defines the method of submitting a task, but does not define a closed method, the Executorservice interface expands the executor interface and adds some methods for life cycle management:
Public interface Executorservice extends Executor { void shutdown (); List<runnable> Shutdownnow (); Boolean IsShutDown (); Boolean isterminated (); Boolean awaittermination (long timeout, timeunit unit) throws Interruptedexception; ... }
The Executorservice life cycle has 3 states: Run, off, and terminated. The Executorservice is in the running state at the time of initial creation. The shutdown method performs a gentle shutdown process: No longer accepts new tasks, while waiting for tasks that have already been committed to complete-including those that have not yet started. The Shutdownnow method will perform a rough shutdown process: It will attempt to cancel all of the tasks in progress, and no longer start a task in the queue that has not yet begun to execute.
After all tasks have been completed, the Executorservice will be transferred to the terminating state. You can call awaittermination to wait for Executorservice to arrive at the terminating state, or to poll for Executorservice if it has been terminated by calling Isterminated. Shutdown is usually called immediately after Awaittermination is called, resulting in a synchronous shutdown of the Executorservice effect.
Thread pool
The core of the executor framework is the thread pool. A thread pool is a resource pool that manages a set of homogeneous worker threads, and performing tasks in the thread pool is more advantageous than assigning a thread to each task. By reusing an existing thread instead of creating a new thread, you can distribute the huge overhead incurred during thread creation and destruction while processing multiple requests. Another additional benefit is that when a request arrives, the worker thread usually already exists, so it does not delay the execution of the task because it waits for the thread to be created, thus improving responsiveness. By properly resizing the thread pool, you can create enough threads to keep the processor busy, while preventing excessive threads from competing with each other for resources and failing to run out of memory for the application.
Threadpoolexecutor defines a thread pool, which is declared as follows:
public class Threadpoolexecutor extends Abstractexecutorservice {...} Public abstract class Abstractexecutorservice implements Executorservice {...}
As you can see, Threadpoolexecutor inherits from the Abstractexecutorservice,abstractexecutorservice implementation of the Executorservice interface, So Threadpoolexecutor also indirectly implemented the Executorservice interface.
Threadpoolexecutor defines a number of constructors, and the following code gives the most important constructors for that class:
Public threadpoolexecutor (int corepoolsize, int maximumpoolsize, long KeepAliveTime, timeunit Unit, blockingqueue<runnable> WorkQueue, Threadfactory threadfactory, Rejectedexecutionhandler handler) {...}
The Corepoolsize, Maximumpoolsize, KeepAliveTime, and unit parameters define the base size, maximum size, and survival time of the thread pool, respectively. Corepoolsize defines the base size of the thread pool, which is the target size of the thread pool, that is, the size of the thread pool when there is no task execution, and that a thread that exceeds this number is created only if the work queue is full. Maximumpoolsize defines the maximum size of the thread pool, which represents the upper limit of the number of threads that can be active concurrently. The KeepAliveTime and unit collectively define the lifetime of a thread, and if a thread's idle time exceeds the time of survival, it is marked as recyclable and is terminated when the thread pool's current size exceeds its base size.
The Workqueue parameter contains a blocking queue for runnable, and when the thread pool reaches its base size, the newly committed task is placed in the blocking queue, and the implementation of the blocking queue consists of three: unbounded queues, bounded queues, and synchronous handoff queues.
The Threadfactory parameter is used to set up the factory where the thread is created, and the thread factory can set a more meaningful name for each thread that is created to conveniently locate the problem.
The handler parameter defines the thread pool saturation policy. When the bounded queue is filled and the thread pool activity threads reach the maximum number of threads, the saturation policy becomes effective. The JDK provides several different rejectedexecutionhandler implementations, namely AbortPolicy, Discardpolicy, Discardoldestpolicy, and Callerrunspolicy. AbortPolicy is the default saturation policy, and the policy throws an unchecked rejectedexecutionexception. The Discardpolicy strategy discards the newly submitted task directly, while the Discardoldestpolicy strategy discards the oldest task in the queue header. The Callerrunspolicy policy implements a throttling mechanism that neither discards the task nor throws an exception, but instead rolls some tasks back to the caller, reducing the traffic to the new task, which does not perform the newly submitted task in a thread pool. Instead, the task is executed in a thread that invokes execute.
Executors Static Factory method
As seen from the previous section, the new threadpoolexecutor need to pass many parameters, which is extremely inconvenient to use. For ease of use, executors provides us with several static factory methods that greatly simplify the creation of thread pools, respectively:
- Newfixedthreadpool:newfixedthreadpool will create a fixed-size thread pool that creates a thread each time a task is committed, until the maximum number of thread pools is reached, and the thread pool size will no longer change;
- Newcachedthreadpool:newcachedthreadpool will create a cacheable thread pool that will reclaim idle threads if the current size of the thread pool exceeds the processing requirements, and when the demand increases, you can add new threads that have no limit on the size of the thread pool.
- Newsinglethreadexecutor:newsinglethreadexecutor is a single-threaded executor that creates a single worker thread to perform a task, and if this thread ends abnormally, another thread is created instead.
Taking Newcachedthreadpool as an example, we can look at the internal implementations of these static factory methods:
public static Executorservice Newcachedthreadpool () { return new Threadpoolexecutor (0, Integer.max_value, 60L , Timeunit.seconds, new synchronousqueue<runnable> ()); }
As you can see, these static factory methods are ultimately called Threadpoolexecutor constructors that specify a thread pool base size of 0, a maximum size of an integer value, a thread survival time of 60s, and a blocking queue as a synchronousqueue. From these parameters it is known that when a thread submits Newcachedthreadpool thread pool, because the base size is 0, it must be larger than the base size, then the task will go into the blocking queue, and the synchronousqueue does not have any capacity inside. and the current number of threads does not reach the maximum number of threads, so the task executes immediately. The task executes with a 60s timeout, and if a new task is called during that time, the new task will run directly on that thread.
Summarize
The use of thread pools can help us to manage threads uniformly, improve thread manageability, and when we write multithreaded code, we should prioritize thread pool creation.
Java Concurrency (Basic knowledge)--executor framework and thread pool