In the past, I always felt that when I bought something, did something, or started from a certain point in time, my life would have a turning point, everything would go smoothly, and it would immediately become more powerful. However, this is not the case. I can't get better right away, nor can I become a fat man in one bite. Reading an article will not allow you to reach the peak of life from now on. I believe that this is a long-term process, and only quantitative changes cause qualitative changes, even if it is slow, without stopping.
[TOC]
How to design a thread pool?
Three steps
This is a common problem, if you are familiar with the principle of thread pool operation, this problem is not difficult. Design and realize one thing, three steps: what is it? Why? How to do it?
What is the thread pool?
The thread pool uses pooling technology to store threads in a "pool" (container). Tasks can be processed with existing idle threads. After processing is completed, they are returned to the container for reuse. If the threads are not enough, they can be dynamically increased according to the rules. When the threads are redundant, the redundant threads can also be killed.
Why use thread pool?
What are the benefits of implementing a thread pool?
- Reduce resource consumption: Pooling technology can reuse threads that have been created, reducing the loss of thread creation and destruction.
- Improve response speed: use existing threads for processing, reducing the time to create threads
- Controllable management threads: threads are scarce resources and cannot be created infinitely. The thread pool can be allocated and monitored uniformly
- Expand other functions: such as a timing thread pool, which can execute tasks on a regular basis
Points to consider
Points to consider in thread pool design:
Thread pool status:
- What are the statuses? How to maintain state?
Thread
- How to encapsulate threads? In which pool is the thread placed?
- How does the thread get the task?
- What are the statuses of threads?
- How to limit the number of threads? Dynamic changes? Automatically scale?
- How did the thread die? How to reuse?
Task
- When there are fewer tasks, you can handle them directly. Where do you put them when there are more tasks?
- The task queue is full, what should I do?
- What queue to use?
If you look at the stage of the task, it can be divided into the following stages:
- How to save tasks?
- How to take the task?
- How to perform the task?
- How to reject a task?
Thread pool status
What are the statuses? How to maintain state?
The status can be set to the following:
- RUNNING: running status, you can accept or process tasks
- SHUTDOWN: Can not accept the task, but can handle the task
- STOP: Can not accept the task, can not process the task, interrupt the current task
- TIDYING: All threads stop
- TERMINATED: the last state of the thread pool
The various states are different, and their state changes are as follows:
The maintenance state, it can be stored with a variable separately and need to ensure that when the modified atomic , in the underlying operating system, modifications of the int is atomic, in 32-bit operating system which, on double
, long
This type of operation on 64-bit values is not atomic. , the state implemented in the JDK and the number of threads in the thread pool are actually the same variable. The upper 3 bits represent the state of the thread pool, and the lower 29 bits represent the number of threads.
The advantage of this design is that it saves space and has advantages when updating at the same time.
Thread-related
How to encapsulate threads? In which pool is the thread placed?
The thread, that is, implements the Runnable
interface. When executing, the start()
start()
, but the method 061341dabc7cee is called after the internal compilation of the run()
method. This method can only be called once, and an error will be reported if it is called multiple times. Therefore, after the threads in the thread pool run, it is impossible to terminate and restart, and can only run forever. cannot be stopped, after the task is executed, there is no task
Threads can run tasks, so when encapsulating threads, suppose the encapsulation is Worker
, Worker
must contain a Thread
, which means the current thread. In addition to the current thread, the encapsulated thread class should also hold tasks, and initialization may be directly given to tasks. Only when the current task is null do you need to get the task.
You can consider using HashSet
to store threads, that is, acting as a thread pool. Of course, HashSet
will have thread safety issues to consider, so we can consider using a reentrant lock such as ReentrantLock
. All threads that add or delete thread pools are required Locked.
private final ReentrantLock mainLock = new ReentrantLock();
How does the thread get the task?
(1) initialize the thread when you can directly specify tasks, such as Runnable firstTask
, the task package to worker
then get worker
inside thread
, thread.run()
when, in fact, running is worker
itself run()
method because worker
itself is realized Runnable
interface, the thread inside is actually itself. Therefore, customization of the ThreadFactory
thread factory can also be achieved.
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread;
Runnable firstTask;
...
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 从线程池创建线程,传入的是其本身
this.thread = getThreadFactory().newThread(this);
}
}
(2) The thread that runs the task should continue to fetch the task. The fetching task must be fetched from the task queue. If there is no task in the task queue, because it is a blocking queue, you can wait. If there is still no task after waiting for a certain period of time, If the number of threads in the thread pool has exceeded the number of core threads and the thread is allowed to die, the thread should be removed from the thread pool and the thread should be terminated.
Fetching tasks and executing tasks is a cycle of work for the threads in the thread pool, unless it will die.
What are the statuses of threads?
Now we are talking about Java
threads in Thread
, one thread at a given point in time, only one state, these states are virtual machine state, does not reflect any operating system thread states, a total of six Kinds/seven states:
NEW
: The thread object is created, but theStart()
method has not been called yet, and the thread that has not been started is in this state.Running
: The running state actually contains two states, but theJava
thread collectively refers to ready and running as runnableRunnable
: Ready state: After the object is created, thestart()
method is called. The thread in this state is still in the runnable thread pool, waiting to be scheduled to obtain theCPU
to use 061341dabc7e8b- Only qualified to execute, not necessarily executed
start()
enters the ready state, thesleep()
or the end ofjoin()
, the thread acquires the object lock, etc. will enter this state.CPU
time slice ends or theyield()
method is actively called, it will also enter this state
Running
: Obtain theCPU
to use 061341dabc7f24 (obtain the CPU time slice), and become running
BLOCKED
: blocking, the thread is blocked on the lock, waiting for the monitor lock, usually a method or code block modified by theSynchronize
WAITING
: To enter this state, you need to wait for other threads to notify (notify
) or interrupt. One thread waits for another thread indefinitely.TIMED_WAITING
: Wait for overtime, automatically wake up after a specified time, return, and will not wait foreverTERMINATED
: The thread has finished executing and has exited. If start() is called after termination, ajava.lang.IllegalThreadStateException
exception will be thrown.
How to limit the number of threads? Dynamic changes? Automatically scale?
The thread pool itself is to limit and make full use of thread resources, so there are two concepts: the number of core threads and the maximum number of threads.
To make the number of threads change dynamically according to the number of tasks, then we can consider the following design (assuming there are constant tasks):
- Come to a task and create a thread for processing until the number of threads reaches the number of core threads.
- After the number of core threads is reached and there are no idle threads, the task is directly placed in the task queue.
- If the task queue is unbounded, it will burst.
- If the task queue is bounded, after the task queue is full, and there are tasks coming, it will continue to create threads for processing. At this time, 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 the maximum number of threads is reached, there are tasks that continue to come over, which will trigger the rejection strategy and process it according to different strategies.
- If the task is continuously processed and completed, the task queue is empty, the thread is idle and there is no task, it will be destroyed within a certain period of time, and the number of threads can be kept at the number of core threads.
It can be seen from the above that the main parameters that control the scaling are the core threads of
maximum number of threads of
task queue of
rejection strategy of 161341dabc849d.
How did the thread die? How to reuse?
The thread cannot be called again start()
multiple times, so it can only be called once, that is, it is impossible for the thread to stop and start again. Then it means that thread reuse is just a constant loop.
The demise just ended its run()
method. When the number of thread pools needs to be automatically reduced, some idle threads will end.
And reuse, in fact, after the task is executed, go to the task queue to fetch the task. If the task is not fetched, it will wait. The task queue is a blocking queue, which is a continuous looping process of
Task-related
When there are fewer tasks, you can handle them directly. Where do you put them when there are more tasks?
When there are few tasks, they are created directly, and the threads are assigned to initialize the tasks, and then they can be executed. When there are many tasks, they are put into the queue, first in, first out.
The task queue is full, what should I do?
When the task queue is full, threads will continue to increase until the maximum number of threads is reached.
What queue to use?
The general queue is just a buffer of limited length. If it is full, the current task cannot be saved. The blocking queue can block and retain the tasks that need to be enqueued, but it will block waiting. Similarly, blocking the queue can also ensure that when there is no task in the task queue, it will block the thread currently acquiring the task, let it enter the wait
state, and release the resources of cpu
Therefore, in the thread pool scenario, blocking queues are actually more necessary.
[Profile of the author] :
Qin Huai, [161341dabc85ad Qinhuai Grocery Store ], the road of technology is not at a time, the mountains are high and the rivers are long, even if it is slow, it will never stop. The world hopes everything will be fast and faster, but I hope I can take every step and write every article, and look forward to communicating with you. If it helps, please give me a thumbs up. It is a great encouragement and recognition to me.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。