Program execution, in essence, is the use of system resources (CPU, memory, disk, network, and so on). How to use these resources efficiently is one direction of our programming optimization evolution. Thread pools are an optimization for CPU utilization.
There are many articles on the web explaining how to use thread pools, so what can I say? I want to look at thread pooling and understand the basic design of pooling technology. Other similar problems can be solved.
Pooling technology
What pooling is, in a nutshell, is saving a large amount of resources in advance for a rainy day. In the case of limited resources, this technology can greatly improve resource utilization and performance.
At present, the typical pooling technologies are thread pool, connection pool, memory pool, object pool and so on.
This paper mainly introduces the implementation principle of the relatively simple thread pool, hoping that readers can draw inferential conclusions, through the understanding of the thread pool, learn and master the underlying principle of all programming in the pool technology, one through one.
Create a thread
Threads are important in Concurrent programming in Java. In Java, creating a thread is simple:
public class App {
public static void main(String[] args) throws Exception {
new Thread(new Runnable() {
@Override
public void run(a) {
System.out.println("Thread running"); } }).start(); }}Copy the code
We can implement a simple thread by creating a thread object and implementing the Runnable interface. You can use a multi-core CPU. When a task terminates, the current thread terminates.
But a lot of times, we perform more than one task. If you create a thread -> execute a task -> destroy a thread every time, there is a significant performance overhead.
Can a thread be created, execute a task, and then execute another task instead of destroying it? This is the thread pool.
This is the idea of pooling by creating several threads in advance and placing them in a pool so that they can be retrieved when needed, avoiding the overhead of repeatedly creating and destroying agents.
Simple use of thread pools
Create a thread pool in Java:
import java.util.concurrent.*;
public class App {
public static void main(String[] args) throws Exception {
ExecutorService executorService = new ThreadPoolExecutor(1.1.60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
executorService.execute(new Runnable() {
@Override
public void run(a) {
System.out.println("abcdefg"); }}); executorService.shutdown(); }}Copy the code
The interfaces the JDK provides to the outside world are also simple. Build one by calling ThreadPoolExecutor directly or by using Executors Static Factory, but it is not recommended.
As you can see, it’s relatively easy for developers to use thread pools in their code, thanks to the number of apis that Java encapsulates. Many times, we need to know what goes on behind these apis so that we can better design and implement our code.
Thread pool constructor
In general, the generic constructor will reflect the data storage structure of the tool or object.
If you think of a thread pool as a company. The company will have regular staff to handle the normal business, and if the workload is heavy, will hire outsourcing staff to do the work. Outsourcing staff can be released during leisure time to reduce the company’s administrative overhead. A company will always hire the maximum number of people because of its cost. If there are still tasks that cannot be handled at this time, go to the demand pool.
- Acc: Gets the call context
- CorePoolSize: The number of core threads, analogous to the number of regular employees, resident threads.
- MaximumPoolSize: the maximum number of threads, the maximum number of employees employed by the company. Number of resident + temporary threads.
- WorkQueue: A queue of redundant tasks that can’t be processed by any number of people. You need to wait.
- KeepAliveTime: Non-core thread idle time, which is how long the outsourcer waits and if there is no work done, is fired.
- ThreadFactory: A factory for creating threads, where properties of the created threads can be handled uniformly. Every company has different requirements for employees, so set the attributes of employees here.
- Handler: What does this mean by thread pool rejection policy? Even when there are too many tasks and not enough people, and the demand pool is full, what about the tasks? The default is to do nothing, throw an exception to tell the task submitter I’m too busy.
Add a task
- The first red box: determine whether the number of working threads has exceeded the maximum number of core threads, if not, the number of new worker threads in new town. And we derive.
- Second red box: check whether the thread pool is running, if yes, whether the task queue is allowed to insert, insert successfully again verify whether the thread pool is running, if no longer running, remove the inserted task, then throw a reject policy. If it runs again and there are no threads, it starts a thread.
- Third red box: if adding a non-core thread fails, it is simply rejected.
Here the logic is a little complicated, draw a flow chart for reference only
Next, let’s see how to add a worker thread. addWork
Adding worker threads
- The first red box: whether you can add worker thread condition filtering.
- Check the thread pool state, if the thread pool is closed, do not submit the task.
- Second red box: Do spin, update the number of threads created.
- First red box: Get the thread pool master lock.
- Second red box: Add thread to workers (thread pool).
- Third red box: Start the new thread. That should make it a lot clearer.
What is retry, one might ask? This is the Goto syntax in Java. Can only be used after break and continue.
Next, let’s see what works is.
Worker threads process queued tasks
- The first red box: whether the task is being executed for the first time or is available from the queue.
- Second red box: After obtaining the task, operate the hook before executing the task.
- The third red box: Execute the task.
- Fourth red box: the hook after executing the task.
These two hooks (beforeExecute, afterExecute) allow us to inherit the thread pool ourselves and do before-and-after processing. Interesting. So much for source code analysis. So let’s do a quick summary.
conclusion
- A thread pool is essentially a hashSet. Extra tasks are placed in a blocking queue.
- The creation of non-core threads is triggered only when the blocking queue is full. So non-core threads just come in on the fly. Until it’s free, and then it shuts itself down.
- The thread pool provides two hooks (beforeExecute, afterExecute) to us, and we inherit the thread pool to do things before and after executing tasks.
- Key technologies of thread pool principle: Lock (CAS), blocking queue, hashSet (resource pool)
Finally, hopefully, it helps you understand thread pools.
There you go. On the way to god. Want to join us?