Thread pools

Each Thread class has a start method. When start is called to start the thread, the JVM calls the run method of that class. The class’s run() method calls the Runnable object’s run() method. We can inherit and override the Thread class and add the Runnable object passed by the continuous call to its start method. This is how thread pools work. Creating a thread pool is done through the ThreadPoolExecutor class.

The benefits of using thread pools

  • Reduced resource consumption: Reduces thread creation and destruction costs by reusing created threads.
  • Improved response time: When a task arrives, it can be executed immediately without waiting for the thread to be created.
  • Improve manageability of threads: Threads are scarce resources. If created without limit, they will not only consume system resources, but also reduce system stability. Thread pools can be used for uniform allocation, tuning, and monitoring.

Core parameter

  • corePoolSize: specifies the number of threads in the thread pool.
  • maximumPoolSize: specifies the maximum number of threads in the thread pool.
  • keepAliveTime: When the current thread pool exceeds corePoolSize, the lifetime of additional idle threads, i.e., multiple times, will be destroyed.
  • unit: Unit of keepAliveTime.
  • workQueue: task queue, tasks that have been submitted but not yet executed.
  • threadFactory: thread factory, used to create threads, usually using the default.
  • handler: Rejection strategy: how to reject tasks when there are too many tasks to handle.

Thread pool working process

  • When a thread pool is created, there are no threads in it. The task queue is passed in as a parameter. However, even if there are tasks in the queue, the thread pool will not execute them immediately.

  • When the execute() method is called to add a task, the thread pool makes the following judgments:

    • If the number of threads running is less thancorePoolSizeImmediately create a thread to run the task;
    • If the number of running threads is greater than or equal tocorePoolSize, then put the task in the queue;
    • If the queue is full and the number of threads running is less thanmaximumPoolSizeCreate a non-core thread to run the task immediately.
    • If the queue is full and the number of running threads is greater than or equal tomaximumPoolSize, the thread pool determines behavior based on the rejection policy.
  • When a thread completes a task, it takes the next task from the queue and executes it.

  • When a thread has nothing to do and exceeds a certain keepAliveTime, the thread pool determines that if the number of threads currently running is greater than corePoolSize, the thread is stopped. So after all the tasks in the thread pool are complete, it will eventually shrink to the size of corePoolSize.

There are three types of ThreadPoolExecutor that can be created:

  • FixedThreadPool: this method returns a thread pool with a fixed number of threads. The number of threads in this thread pool is always the same. When a new task is submitted, it is executed immediately if there are idle threads in the thread pool. If no, the new task is temporarily stored in a task queue. When a thread is idle, the task in the task queue will be processed.
  • SingleThreadExecutorThe: method returns a thread pool with only one thread. If additional tasks are submitted to the thread pool, the tasks are stored in a task queue and executed in a first-in, first-out order until the thread is idle.
  • CachedThreadPool: This method returns a thread pool that can adjust the number of threads as needed. The number of threads in the thread pool is uncertain, but if there are free threads that can be reused, reusable threads are preferred. If all threads are working and a new task is submitted, a new thread is created to process the task. All threads will return to the thread pool for reuse after completing the current task.

Built-in rejection policies in the JDK

  • AbortPolicy: The default value is to throw an exception to prevent the system from running normally.

  • CallerRunsPolicy: Rejected tasks run in the main thread, so the main thread is blocked. Other tasks are submitted to the thread pool only after the rejected task has completed execution.

  • DiscardOldestPolicy: Discards the oldest request, i.e. a task to be executed, and attempts to resubmit the current task.

  • DiscardPolicy: Discard new tasks without processing them.

All the preceding built-in rejection policies implement the RejectedExecutionHandler interface. If the preceding policies still cannot meet actual requirements, you can extend the RejectedExecutionHandler interface.

Second, the ThreadLocal

ThreadLocal is mainly used for data isolation. The data filled by ThreadLocal belongs only to the current thread, and the data of variables is relatively isolated from other threads. In a multi-threaded environment, the variables of ThreadLocal are prevented from being tampered with by other threads. You can use the get() and set() methods to get the default value or change it to the value of a copy of the current thread.

public class Thread implements Runnable {
    // The ThreadLocal value associated with this thread. Maintained by the ThreadLocal class
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // The InheritableThreadLocal value associated with this thread. Maintained by the InheritableThreadLocal class
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; . }Copy the code

ThreadLocalMap is obtained from a variable called threadLocals for the current Thread. ThreadLocalMap understands the custom HashMap implemented for the ThreadLocal class. This variable is null by default and is created only when the current thread calls the Set or GET methods of the ThreadLocal class. However, it does not implement Map interface, and its Entry key inherits WeakReference, and it does not see next in HashMap, so there is no linked list.

Suppose ThreadLocal is used up in the business code and ThreadLocal Ref is reclaimed; A Key in a ThreadLocalMap strongly references a ThreadLocal, causing the ThreadLocal to be strongly referenced and unable to be reclaimed.

If ThreadLocal is not strongly referenced externally, the key will be cleaned up during garbage collection, but the value will not be. This will result in an Entry with a null key in ThreadLocalMap. If we don’t do anything, value will never be collected by GC, which could result in a == memory leak. The ThreadLocalMap implementation already takes this into account by calling the set(), get(), and remove() methods to clean up records with a null key. It is best to call the remove() method manually after using ThreadLocal.

It is not clear if there are any references to the value other than the map reference. If there are no other references, the value will be killed by GC while our ThreadLocal is still in use, resulting in a null value error.

public void set(T value) {
    Thread t = Thread.currentThread(); // Get the current thread
    ThreadLocalMap map = getMap(t); // Get the ThreadLocalMap object
    if(map ! =null) // Check whether the object is empty
        map.set(this, value); // not null set
    else
        createMap(t, value); // Create a map object for null
}
Copy the code

A thread can have multiple TreadLocal for different types of objects, but they will all be placed in the ThreadLocalMap of the current thread. The key of a ThreadLocalMap is the ThreadLocal object, and the value is the value that the ThreadLocal object sets by calling the set method.

Insert a hashCode into the table based on the hash value of the ThreadLocal object: If the current position I is empty, an Entry object is initialized and placed on position I. (2) If position I is not empty, and if the key of the Entry object happens to be the key to be set, the value in the Entry is refreshed. ③ If position I is not empty and key does not equal Entry, find the next empty position until it is empty.

During get, it will also locate the position in the table according to the hash value of the ThreadLocal object, and then determine whether the key in the Entry object of this position is the same as the key of the GET. If not, it will determine the next position. If the set and GET conflict seriously, the efficiency is still very low.

ThreadLocal data for shared threads

Use InheritableThreadLocal to create an instance of InheritableThreadLocal that can be accessed by multiple threads. We then get the value set by this InheritableThreadLocal instance in the child thread. If the thread’s inheritThreadLocals variable is not empty and the parent thread’s inheritThreadLocals also exists, So I use init() to transfer the inheritThreadLocals from the parent thread to the inheritThreadLocals from the current thread.

Why not define ThreadLocalMap in Thread class?

It seems more logical to define ThreadLocalMap inside the Thread class, but ThreadLocalMap does not require a Thread object to operate on, so defining it inside the Thread class simply adds unnecessary overhead. The ThreadLocal class is responsible for creating a ThreadLocalMap. A ThreadLocalMap is created for the current thread only when the first ThreadLocal is set in the thread. All other ThreadLocal variables will then use a ThreadLocalMap.

In summary, ThreadLocalMaps are not required, definitions add cost to threads, and definitions are created on demand in ThreadLocal.

Application scenarios

Spring transaction isolation level source code, using Threadlocal, to ensure that a single thread in the database operation is using the same database connection, at the same time, using this way can enable the business layer to use transactions do not need to perceive and manage connection objects, through the propagation level, Skillfully manage switching, suspending, and resuming between multiple transaction configurations.


We used SimpleDataFormat’s parse() method and there was a Calendar object inside. Calling SimpleDataFormat’s parse() method would call Calendar.clear() first. Calendar.add() is then called. If one thread first calls add() and then another thread calls clear(), the parse() method will parse at the wrong time.

The simple solution to this problem was to have each thread create its own SimpleDataFormat, so we wrapped SimpleDataFormat with a thread pool and ThreadLocal. Calling initialValue lets each thread have a copy of SimpleDataFormat, which addresses thread-safety issues and improves performance.