Do you really want to know about the thread pool?

foreword

Only the bald head can become stronger

Looking back at the front:

This article is mainly to explain the thread pool. This is my penultimate article on multithreading, and there will be a deadlock later . Mainly go through the basics of multi-threading , and have the opportunity to go further in the future !

Then let's start. If there are mistakes in the article, please bear with me and correct me in the comment area~

Disclaimer: This article uses JDK1.8

1. Introduction to the thread pool

A thread pool can be thought of as a collection of threads . When there is no task, the thread is in an idle state. When the request comes: the thread pool allocates an idle thread to the request. After the task is completed, it returns to the thread pool to wait for the next task** (instead of destroying it) . This achieves the reuse of threads**.

Let's see if the thread pool is not used like this:

  • Start a new thread for every request !

public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(80);
        while (true) {
			// 为每个请求都创建一个新的线程
            final Socket connection = socket.accept();
            Runnable task = () -> handleRequest(connection);
            new Thread(task).start();
        }
    }
    private static void handleRequest(Socket connection) {
        // request-handling logic here
    }
}

Opening a new thread for each request is theoretically possible , but has drawbacks :

  • The overhead of the thread lifetime is very high . Each thread has its own life cycle, creating and destroying threads may take more time and resources than processing the client's tasks, and there will be some idle threads that also take resources .
  • The stability and robustness of the program will decrease, and each request will open a thread. If there is a malicious attack or too many requests (insufficient memory), the program can easily crash.

So: our thread is best managed by the thread pool , which can reduce the management of the thread life cycle and improve the performance to a certain extent.

Second, the thread pool API provided by JDK

JDK provides us with the Executor framework to use the thread pool, which is the basis of the thread pool .

  • Executor provides a mechanism to separate "task submission" from "task execution" (decoupling)

Let's take a look at the overall api architecture of the JDK thread pool:

 

 

Next, let's take a look at these APIs:

Executor interface:

 

 

ExecutorService interface:

 

 

AbstractExecutorService class:

 

 

ScheduledExecutorService interface:

 

 

ThreadPoolExecutor类:

 

 

ScheduledThreadPoolExecutor类:

 

 

2.1 ForkJoinPool thread pool

In addition to the ScheduledThreadPoolExecutor and ThreadPoolExecutor class thread pools, there is another thread pool added to JDK1.7 : ForkJoinPool thread pool

So our class diagram can become more complete:

 

 

A new thread pool in JDK1.7, like ThreadPoolExecutor, also inherits AbstractExecutorService. ForkJoinPool is one of the two core classes of the Fork/Join framework. Compared with other types of ExecutorService, the main difference is the use of work-stealing algorithm (work-stealing) : all threads in the pool will try to find and execute tasks that have been submitted to the pool or created by other threads. In this way, few threads will be idle, which is very efficient. This makes it possible to efficiently handle the following scenarios: most cases where a large number of subtasks are spawned by a task; cases where a large number of small tasks are submitted to the pool from external clients.

source:

2.2 Supplement: Callable and Future

After learning the thread pool, we can easily find that many APIs have two things called Callable and Future.


	Future<?> submit(Runnable task)
	<T> Future<T> submit(Callable<T> task)

In fact, they are not very deep things.

We can simply think: Callable is an extension of Runnable .

  • Runnable has no return value and cannot throw checked exceptions, while Callable can !

 

 

That is to say: when our task needs to return a value, we can use Callable!

Future is generally considered to be the return value of Callable, but it actually represents the life cycle of the task (of course, it can obtain the return value of Callable)

 

 

Just look at their usage:


public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(2);

		// 可以执行Runnable对象或者Callable对象代表的线程
		Future<Integer> f1 = pool.submit(new MyCallable(100));
		Future<Integer> f2 = pool.submit(new MyCallable(200));

		// V get()
		Integer i1 = f1.get();
		Integer i2 = f2.get();

		System.out.println(i1);
		System.out.println(i2);

		// 结束
		pool.shutdown();
	}
}

Callable task:


public class MyCallable implements Callable<Integer> {

	private int number;

	public MyCallable(int number) {
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}

}

After executing the task, you can get the data returned by the task :

 

 

3. Detailed explanation of ThreadPoolExecutor

This is the most used thread pool, so this article will focus on it.

Let's take a look at the top note:

 

 

3.1 Internal state

 

 

The variable ctl is defined as AtomicInteger, which records two pieces of information, "the number of tasks in the thread pool" and "the state of the thread pool" .

 

 

Status of the thread:

  • RUNNING: The thread pool can accept new tasks and process newly added tasks.
  • SHUTDOWN: The thread pool cannot accept new tasks , but can process added tasks.
  • STOP: The thread pool does not receive new tasks, does not process tasks that have been added, and interrupts tasks being processed .
  • TIDYING: When all tasks have been terminated , the "number of tasks" recorded by ctl is 0, and the thread pool will become TIDYING state. When the thread pool becomes TIDYING state, the hook function terminated() will be executed. terminated() is empty in the ThreadPoolExecutor class. If the user wants to perform corresponding processing when the thread pool becomes TIDYING, it can be achieved by overloading the terminated() function.
  • TERMINATED: The thread pool is completely terminated .

 

 

Transition between states:

 

 

3.2 Pools that have been implemented by default

Below I list three more common implementation pools:

  • newFixedThreadPool
  • newCachedThreadPool
  • SingleThreadExecutor

If you understand the corresponding strategy above and the number of threads, it should not be too difficult to understand.

3.2.1newFixedThreadPool

A thread pool with a fixed number of threads, which will return a thread pool with corePoolSize and maximumPoolSize equal .


   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

3.2.2newCachedThreadPool

A very flexible thread pool . For a new task, if there are no idle threads in the thread pool at this time, the thread pool will create a new thread to process the task without hesitation .


    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3.2.3SingleThreadExecutor

Executor using a single worker thread


public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.3 Construction method

After we read the default implementation pool above and the corresponding properties, let's go back to the construction method to see

  • The constructor allows us to customize (extend) the thread pool

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. Specify the number of core threads
  2. Specify the maximum number of threads
  3. Allow thread idle time
  4. time object
  5. blocking queue
  6. thread factory
  7. task rejection policy

To summarize the main points of these parameters again:

Thread count points :

  • If the number of running threads is less than the number of core threads, create a new thread to handle the request
  • If the number of running threads is greater than the number of core threads and less than the maximum number of threads, a new thread will be created when the queue is full
  • If the number of core threads is equal to the maximum number of threads, a fixed size connection pool will be created
  • If the maximum number of threads is set to infinity , then the thread pool is allowed to fit any number of concurrency

Thread idle time points:

  • The current number of threads is greater than the number of core threads. If the idle time has exceeded, the thread will be destroyed .

Queuing strategy points :

  • Synchronous handover: not put into the queue, but wait for the thread to execute it . If the current thread is not executing, it is likely that a new thread will be opened for execution.
  • Unbounded strategy: If the core thread is working, the thread will be put into the queue . So the number of threads will not exceed the number of core threads
  • Bounded strategy: can avoid resource exhaustion , but reduce throughput to a certain extent

When the thread is closed or the number of threads is full and the queue is saturated, there is a situation where the task is rejected:

Deny task policy:

  • throw exception directly
  • Use the caller's thread for processing
  • just drop this task
  • drop the oldest task

Four, execute execution method

The execute execution method is divided into three steps, which are written in the code in the form of comments~


    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
		//如果线程池中运行的线程数量<corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

		//如果线程池中运行的线程数量>=corePoolSize,且线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,就再次检查线程池的状态,
			// 1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。
			// 2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
		// 如果以上两种case不成立,即没能将任务成功放入阻塞队列中,且addWoker新建线程失败,则该任务由当前 RejectedExecutionHandler 处理。
        else if (!addWorker(command, false))
            reject(command);
    }

Fifth, the thread pool is closed

ThreadPoolExecutor provides shutdown()and shutdownNow()two methods to close the thread pool

shutdown() :

 

shutdownNow():

 

 

the difference:

  • After calling shutdown(), the thread pool state immediately changes to SHUTDOWN , and when shutdownNow() is called, the thread pool state immediately changes to STOP .
  • shutdown() waits for the task to complete before interrupting the thread, while shutdownNow() interrupts the thread without waiting for the task to complete .

6. Summary

This blog post mainly briefly introduces the multi-threaded structure system, and talks about how the most commonly used ThreadPoolExecutor thread pool is used~~~

I hope to write the deadlock out tomorrow, so stay tuned~~~

There are a few remaining thread pools (references given):

References:

If there are any mistakes in the article, please correct me, and we can communicate with each other. Students who are used to reading technical articles on WeChat and want to get more Java resources can follow WeChat public account: Java3y .

Table of Contents Navigation for Articles :

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325301704&siteId=291194637