Before, I always thought that by buying one thing, doing one thing, or starting at a certain point in time, my life would turn around and everything would go smoothly and instantly become powerful. But that’s not the case. I’m not going to get better all at once. I’m not going to get fat all at once. Reading an article can not let you go to the peak of life, more and more believe that this is a long-term process, only quantitative change causes qualitative change, even if slow, galloping and never stop.

How to design a thread pool?

Three steps

This is a common problem, and not difficult if you are familiar with how thread pools work. There are three steps to designing something: What is it? Why is that? How to do?

What is a thread pool?

Thread pooling uses pooling technology to store threads in a “pool” (container), where incoming tasks can be processed with existing idle threads, and then returned to the container for reuse. If there are not enough threads, they can be added dynamically according to the rules. When there are more threads, they can also be killed.

Why thread pools?

What are the benefits of implementing thread pools?

  • Reduced resource consumption: Pooling techniques can reuse threads that have already been created, reducing thread creation and destruction costs.
  • Improved response time: Use existing threads for processing, reducing thread creation time
  • Management thread control: Threads are scarce resources and cannot be created indefinitely. Thread pools can be uniformly allocated and monitored
  • Expand other features: such as timed thread pools, which allow timed tasks to be executed

Points to consider

Points to consider in thread pool design:

  • Thread pool status:

    • What are the states? How do I maintain state?
  • thread

    • How do threads wrap? Which pool is the thread in?
    • How do threads get tasks?
    • What states do threads have?
    • How to limit the number of threads? Dynamic change? Automatic scaling?
    • How do threads die? How to reuse?
  • task

    • Less tasks can be handled directly, and more, where to put them?
    • The task queue is full. What should I do?
    • What queue?

From the perspective of task stages, it can be divided into the following stages:

  • How do I save tasks?
  • How do I get tasks?
  • How to perform the task?
  • How to reject a task?

Thread pool state

What are the states? How do I maintain state?

The status can be set to the following:

  • RUNNING: Indicates that tasks can be accepted or processed
  • SHUTDOWN: cannot accept tasks, but can process them
  • STOP: The current task cannot be interrupted or accepted
  • TIDYING: All threads stop
  • TERMINATED: The last state of the thread pool

The various states are different, and their states vary as follows:

To maintain state, a variable can be stored separately, and changes need to be atomic. In the underlying operating system, changes to int are atomic, but in the 32-bit operating system, operations on 64-bit values such as double and long are not atomic. In addition, the state implemented in the JDK is actually the same variable as the number of threads in the thread pool, with three bits higher representing the state of the thread pool and 29 bits lower representing the number of threads.

The advantage of this design is that it saves space and has the advantage of updating at the same time.

Thread related

How do threads wrap? Which pool is the thread in?

A thread that implements the Runnable interface calls the start() method when it executes, but internally compiles the run() method, which can only be called once. Therefore, a thread running in a thread pool cannot be terminated and started again, but can only continue running. Since it cannot be stopped, there is no task coming after the execution of the task, only polling and fetching the task

Threads can run tasks. Therefore, when encapsulating threads, assuming that the encapsulation becomes Worker, Worker must contain a Thread, representing the current Thread. Besides the current Thread, the encapsulated Thread class should also hold tasks, and initialization may be directly given to the task. You need to obtain the task only when the current task is null.

Consider using a HashSet to store threads, that is, to act as a thread pool. Of course, there are thread-safety concerns with hashsets, so we can consider using a ReentrantLock, for example, to lock any thread that adds or deletes from a thread pool.

    private final ReentrantLock mainLock = new ReentrantLock();
Copy the code

How do threads get tasks?

(1) When initializing threads, tasks can be directly specified, such as Runnable firstTask, and tasks can be encapsulated into worker, and then threads in worker can be obtained. Thread.run (), In fact, it is the run() method of the worker itself that runs, because the worker itself implements the Runnable interface and the thread in it is itself. Therefore, you can also customize the ThreadFactory ThreadFactory.

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        finalThread thread; Runnable firstTask; . Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            // Create a thread from the thread pool and pass in itself
            this.thread = getThreadFactory().newThread(this); }}Copy the code

(2) the task of running the threads, should continue to take the task, take the task must be need from the task queue inside, if there is no task in task queue is due to the blocking queue, you can wait, if wait for some time after, there is still no task, if the thread pool threads has exceeded the core number of threads, and allow the thread die, The thread should be removed from the thread pool and terminated.

Fetching and executing tasks is a recurring task for threads in the thread pool, unless it dies.

What states do threads have?

A Thread can only be in one state at a given point in time. These states are virtual machine states and do not reflect any operating system Thread states. There are six or seven states in total:

  • NEW: The thread object is created, but the Start() method has not been called, and threads that have not been started are in this state.

  • There are two states, but Java threads call both ready and Running runnable

    • RunnableReady state: called after the object is createdstart()Method, the thread in that state is still in the runnable thread pool, waiting to be scheduled and fetchedCPUThe right to use the
      • Just qualified to execute, not necessarily will execute
      • start()And then in the ready state,sleep()The end orjoin()The thread enters this state when it acquires an object lock, etc.
      • CPUEnd of time slice or active callyield()Method will also enter this state
    • Running: get to theCPUThe right to use (get CPU time slices) becomes running
  • BLOCKED: A thread that blocks a lock and waits for a monitor lock, typically a method or code block modified with the Synchronize keyword

  • WAITING: state in which one thread is WAITING indefinitely for another thread to notify or interrupt.

  • TIMED_WAITING: timeout waiting, automatically wakes up after a specified time, returns, does not wait forever

  • TERMINATED: The thread is TERMINATED. If terminated call start (), will be thrown. Java lang. IllegalThreadStateException anomalies.

How to limit the number of threads? Dynamic change? Automatic scaling?

The thread pool itself is designed to limit and maximize the use of thread resources, so there are two concepts: the number of core threads and the maximum number of threads.

To dynamically change the number of threads based on the number of tasks, consider the following design (assuming that there are continuous tasks) :

  • Create a thread for each task until the number of threads reaches the core number.
  • When the number of core threads is reached and there are no idle threads, incoming tasks are put directly into the task queue.
  • If the task queue is unbounded, it will burst.
  • If the task queue is bounded, after the task queue is full, more tasks will be created and processed. At this point, the number of threads is greater than the number of core threads until the number of threads is equal to the maximum number of threads.
  • After reaching the maximum number of threads, there are still tasks coming, which will trigger the rejection policy and process according to different policies.
  • If the task is completed continuously, the task queue is empty, and the thread is idle, it will be destroyed within a certain period of time, so that the number of threads remain at the number of core threads.

As can be seen from the above, the main parameters controlling scaling are the number of core threads, the maximum number of threads, the task queue and the rejection policy.

How do threads die? How to reuse?

A thread cannot be called start() more than once, so it cannot stop and start again. This means that thread reuse is just a continuous loop.

The death simply ends its run() method, allowing some idle threads to die when the thread pool needs to be shrunk automatically.

However, reuse actually means that after executing a task, you go to the task queue to get the task. If you cannot get the task, you will wait. The task queue is a blocking queue, which is an endless cycle.

Tasks related to

Less tasks can be handled directly, and more, where to put them?

When the task is small, come to create directly, give the thread to initialize the task, can start to execute, when the task is large, put it into the queue, first in first out.

The task queue is full. What should I do?

When the task queue is full, threads continue to be added until the maximum number of threads is reached.

What queue?

A normal queue is just a buffer of limited length. If it is full, it cannot save the current task. A blocking queue can keep the current task by blocking, but it will block waiting. Similarly, the blocking queue can also ensure that when there is no task in the task queue, the thread that obtains the task will be blocked to enter the wait state and release CPU resources. Therefore, in the case of thread pools, blocking queues is actually necessary.

[Author profile] : Qin Huai, public number [Qin Huai Grocery store] author, the road of technology is not at that time, mountain high water long, even slow, chi and not stop. The world wants everything to be fast and faster, but I hope I can take every step, write every article, and look forward to communicating with you. If it is helpful, please give me a thumbs-up, which is a great encouragement and recognition to me.