If there are a large number of concurrent threads, and each thread executes a short task and then terminates, creating threads frequently can greatly reduce the efficiency of the system because of the time it takes to create and destroy threads frequently.

So is there a way that threads can be reused, that they can finish a task and not be destroyed, but can continue to perform other tasks? In Java, this can be achieved through thread pools. Today we are going to take a closer look at Java thread pools,

We’ll start with the methods in the core ThreadPoolExecutor class, explain how it works, give examples of how to use it, and finally discuss how to properly size the thread pool.

1. The thread pool create Java. Uitl. Concurrent. The ThreadPoolExecutor class is the core of a thread pool class, by looking at the source code, we can know this class inherits the AbstractExecutorService abstract classes. Let’s look at the constructor in the ThreadPoolExecutor class:

  • The ThreadPoolExecutor class has four constructors, and the first three actually call the last constructor, the one with the most arguments.
  • A constructor

Let’s look at what each parameter in the constructor means:

  • corePoolSize

    • The size of the core pool has a lot to do with how thread pools are implemented. After a thread pool is created, by default, there are no threads in the pool, but instead wait for a task to arrive before creating a thread to execute the task, unless the prestartAllCoreThreads() or prestartCoreThread() methods are called. As the names of these two methods suggest, The corePoolSize thread or one thread is created before the task arrives. By default, after a thread pool is created, the number of threads in the thread pool is zero. When a task arrives, a thread is created to execute the task. When the number of threads in the thread pool reaches corePoolSize, the incoming task is placed in the cache queue.
  • maximumPoolSize

    • Maximum number of threads in the thread pool. This parameter indicates the maximum number of threads that can be created in the thread pool.
  • keepAliveTime

    • The thread lifetime, which indicates how long a thread can be held without a task being executed before it terminates.
    • By default, keepAliveTime works only when the number of threads in the thread pool is greater than corePoolSize, until the number of threads in the thread pool is no greater than corePoolSize. If a thread is idle for a keepAliveTime, it terminates until the number of threads in the thread pool does not exceed corePoolSize. But if the allowCoreThreadTimeOut(Boolean) method is called, the keepAliveTime parameter will also work until the number of threads in the pool is zero if the number of threads in the pool is not greater than corePoolSize.
  • unit

    • The keepAliveTime parameter is a unit of time. There are 7 values for keepAliveTime. There are 7 static properties in TimeUnit:
      • TimeUnit.DAYS; / / day
      • TimeUnit.HOURS; / / hour
      • TimeUnit.MINUTES; / / minute
      • TimeUnit.SECONDS; / / SEC.
      • TimeUnit.MILLISECONDS; / / ms
      • TimeUnit.MICROSECONDS; / / subtle
      • TimeUnit.NANOSECONDS; / / nanoseconds
  • workQueue

    • A blocking queue is used to store tasks waiting to be executed. The selection of this parameter is also important and can have a significant impact on the running of the thread pool. Generally, there are several options for blocking queues:
      • ArrayBlockingQueue;
      • LinkedBlockingQueue;
      • SynchronousQueue;
  • threadFactory

    • Thread factory, used to create threads, mainly for the name of the thread, the default factory thread name pool-1-thread-3.
  • Handler

    • Reject policy, called when the thread pool is exhausted and the queue is full.
      • ThreadPoolExecutor. AbortPolicy: discard task and throw RejectedExecutionException anomalies.
      • ThreadPoolExecutor. DiscardPolicy: discard task too, but I don’t throw an exception.
      • ThreadPoolExecutor. DiscardOldestPolicy: discard queue in front of the task, and then to try to perform a task (repeat)
      • ThreadPoolExecutor. CallerRunsPolicy: handle the tasks by the calling thread

These are just a few of the parameters used to create a thread pool, and it’s not uncommon for interviewers to ask what these parameters mean. The following figure shows the thread pool execution flow

  1. If the number of current worker threads is smaller than the core thread, the core thread is created to execute the task.
  2. If the current thread is larger than the number of core threads, judge whether the wait queue is full, if not, add tasks to the wait queue, if the number of worker threads is 0, create non-core threads, and pull tasks from the wait queue to execute.
  3. Finally, if the queue is full, create a non-core thread to execute the task. If the creation fails, the task is rejected.

ThreadPoolExecutor is a thread pool implementation class that can be used either by custom thread pools or by system-provided thread pools. Execute Runnable tasks using the execute(Runnable Command) method of the class. So let’s look at the execute method first

  1. Determine whether the current number of active threads is smaller than corePoolSize. If so, call addWorker to create a thread to execute the task
  2. If not less than corePoolSize, the task is added to the workQueue queue.
  3. If the workQueue fails to be placed, a thread is created to execute the task, and if the thread fails to be created (if the current number of threads is at least maximumPoolSize), a reject(internal call to handler) is called to reject the task.

AddWorker () = execute(); addWorker() = execute(); This code is created when a non-core thread is created, i.e. core equals false. Check whether the current number of threads is greater than or equal to maximumPoolSize. If it is greater than or equal to maximumPoolSize, return false.

Create worker object, pass Runnable as parameter, and take out Thread object from worker to judge a series of conditions. Turn on the start() method of Thread and the Thread starts running. So the worker object must contain a Thread and a Runnable. 3 to be executed. Then go to Worker to see its implementation

  • Each worker is a thread, and it contains a firstTask, that is, the task to be executed first during initialization.
  • It is the runWorker() method that ultimately performs the task

4. Let’s look at the logic of the runWorker method

The thread calls runWoker, and the getTask method is called in the while loop to read the task from the workerQueue and then execute the task. As long as the getTask method does not return NULL, the thread will not exit. 5. Finally, look at the getTask method implementation

  • Regardless of allowCoreThreadTimeOut, this variable defaults to false. Wc >corePoolSize determines whether the current number of threads is greater than corePoolSize.
  • If the current thread count is greater than corePoolSize, the poll method of workQueue is called to get the task and the timeout is keepAliveTime. If the keepAliveTime is exceeded, poll returns null, the while exits sequentially, and the thread completes.

We have recently collected an interview materials, covering Java core technology, JVM, Java concurrency, SSM, microservices, databases, data structures and other technical points. Interested students can +VX: babadeerya520 for relevant materials.