On principle ThreadPoolExecutor thread pool

Instructions

Here we use the simplest form to create a thread pool, the purpose is to show you use to use ThreadPoolExecutor

    public static void main(String[] args) {
    	// 创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 10, 200, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));

        for (int i = 0; i < 10; i++) {
        	// 使用线程池
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread() + " -> run");
                }
            };
        }
        // 销毁线程池
        threadPool.shutdown();
    }
复制代码

It's that simple, it demonstrates a thread pool finished, including the creation, use and destruction of the whole process

Explain the concept

In order to prepare for the next source code analysis, come here to explain some of the concepts associated with the thread pool ThreadPoolExecutor

parameter

Constructors ThreadPoolExecutor the need to pass a lot of parameters, the meaning of these parameters is also very important, I hope you can remember, the constructor parameters are listed below in order:

  • corePoolSize: a core number of threads, without the active set, the core threads are not automatically destroyed, more than the number of threads in core thread is automatically destroyed according to the wait time set
  • maximumPoolSize: maximum number of threads maximum number of threads, thread pool that can be accommodated
  • keepAliveTime: the idle thread (non-core thread) after the waiting time will be automatically destroyed keepAliveTime
  • unit: keepAliveTime time unit
  • workQueue: task queue, if the number of threads than the core number of threads, the new task will first enter the queue, and then do the next judge, for cushion
  • threadFactory (optional): a thread factory used to create threads is set, usually used to set thread name
  • handler (optional): deny policy, when the queue is full or the thread pool is full, the class provides four deny policy

Which can tell much, it is worth mentioning that the object is a workQueue BlockingQueue type, or generally used ArrayBlockingQueue LinkedBlockingQueue

There is a handler in ThreadPoolExecutor have the following four optional parameters:

  • AbortPolicy (default): discarded and thrown task
  • CallerRunsPolicy: Let's call the execute method thread to perform the task
  • DiscardPolicy: discard task, nothrow
  • DiscardOldestPolicy: dropped from the queue the longest waiting tasks (task queue head), followed by addition of the task queue
Thread pool status

ThreadPoolExecutor class has a variable ctl, I like to call it the thread pool flag code, is a 32-bit integer, high three state flag for the thread pool, and the remaining 29 represent the current number of threads in the pool

On this high-three, there are several state identification:

  • -1: RUNNING, running, indicates that the thread pool work
  • 0: SHUTDOWN, closed state, indicates that the thread pool worker ready to end, can not accept new tasks, but can handle queued in the task queue task
  • 1: STOP, stop state, it indicates that the thread pool to stop working, can not accept new tasks, can not handle the task queue, and it will interrupt task execution
  • 2: TIDYING, knock off state, indicates that the thread pool is about to end, all the tasks have been terminated, the number of worker threads is 0, it will run immediately terminated () hook method End of Life thread pool
  • 3: TERMINATED, end state, indicating the end of the thread pool life, end terminated () method call

Way conversion between these states are interested can also look at:

  • After calling the shutdown method:> SHUTDOWN - RUNNING
  • (RUNNING or SHUTDOWN) -> STOP: After method invocation shutdownNow
  • SHUTDOWN -> TIDYING: When the worker thread pool and queues are empty
  • After terminated method is finished:> TIDYING - STOP
Execution policy

After about a job submission thread pool, what kind of behavior will perform the following steps should be able to say very clear:

  1. Submit a job to the thread pool
  2. Determine whether the number of worker threads in the pool is smaller than the core number of threads, if less than, you add a thread, and then perform the task
  3. Whether the queue is full, if full, proceeds to Step 5, otherwise add it to the queue
  4. Remove the task from the queue, if the removal fails, call reject strategies to deal with
  5. Determining whether the thread pool is full, if full, refused to call the strategy to deal with, otherwise the next step
  6. Create a new thread to thread pool to perform the task

After reading this section may not deep impression on you, do not even understand, it does not matter, our next step is to analyze the source code from these steps in the end is how to achieve

Source explained

execute()

Compared with the framework source code, JDK source code is particularly close to the people, we focus only on the one way you can go directly to the execute method here, this method is the core of the whole thread pool method, this method to read a student understand the workflow thread pool, its source code is as follows:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
复制代码

Do not rush to see, here I would like to explain the meaning of some may be some very good way to understand the emergence of which, until the smoke back again explain several important ways:

  • workerCountOf: get the number of the current thread pool
  • isRunning: to determine whether the thread pool in normal operation
  • addWorker: add a task to the thread pool

Well, according to these methods provide, we marked with detailed notes, come back to read through this method:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // 获取线程池标识码
        int c = ctl.get();
        
        // 如果工作线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
        	// 就把该任务添加到线程池中
            if (addWorker(command, true))
                return;
            // 如果添加失败,就更新线程池标识码
            c = ctl.get();
        }
        // 如果线程池在运行状态,且队列能够接受该任务
        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);
        }
        // 添加任务到工作线程中
        else if (!addWorker(command, false))
        	// 如果添加失败,就调用拒绝策略
            reject(command);
    }
复制代码

We summarized above and in exactly the same

addWorker(Runable, Boolean)

If you just want to learn to perform a process, and that this article is over for you, otherwise, only now the real main event, we look at the source code:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取状态码
            int rs = runStateOf(c);

			// 如果线程池不在运行状态,且以下状态不成立:
			// 		线程池已经调用shutdown()停止运行,同时要添加的任务为空但任务队列不为空
			// 就不进行任务的添加操作,直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                // 返回添加失败
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // 如果core为true,就判断是否超过核心线程数,否则就判断是否超过最大线程数
                // 如果超过了限制就直接返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 通过死循环,不断尝试自增工作线程的数量
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 自增成功,重新获取一下线程标识码的值
                c = ctl.get();
                // 如果线程状态发生改变,就返回重试
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        // Worker实现了Runnable接口,是经过封装后的任务类
        Worker w = null;
        try {
        	// 将任务封装为Worker对象,
        	// 同时会调用线程工厂的newThread方法来生成一个执行该任务的线程
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                // 以下为同步方法,需要使用Lock加锁
                mainLock.lock();
                try {
                	// 获取线程池状态
                    int rs = runStateOf(ctl.get());

					// 如果运行状态正常,
					// 或虽然调用shutDown()方法停止线程池,但是待添加任务为空
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果线程已经启动,就表示不能再重新启动了,则抛出异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 将该线程添加到workers中
                        // workers包含所有的工作线程,必须工作在同步代码块中
                        workers.add(w);
                        int s = workers.size();
                        // largestPoolSize是整个线程池曾达到过的最大容量
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                	// 解锁操作必须写在finally语句中,因为Lock在抛出异常时不会自动解锁
                    mainLock.unlock();
                }
                // 如果添加任务成功,就启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	// 不管怎么样,只要启动失败,就会进行操作的回滚
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回是否启动成功,而不是是否添加任务成功
        return workerStarted;
    }
复制代码

I put the whole method is divided into four parts:

  1. Pre-check: to ensure that the task queue thread pool and can be operated normally
  2. Pre-operation: in advance from increasing the number of worker threads through the spin lock to ensure synchronous
  3. Add Task: The task to be performed becomes encapsulated Worker object, added to the task set
  4. Tasks and abnormal rollback: start worker threads, if the operation failed before it will start to be rolled back

If you have not read place, with comments in the code should be able to read about the same, if only to glance at my conclusion complained not read, it is recommended to find another elsewhere, since I am here to make up the complete comment still I hope that we can carefully read, summarized here is just a simple summary

There is a piece of code that is part of the pre-check a little difficult to understand, this is the following:

            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
复制代码

I'll talk about in detail here, first of all, to determine where the two conditions, if the first condition is met and the second condition is not satisfied, returns to the Direct false, that is a direct failure. We turn to, will continue under what circumstances? This way it simplifies a lot of conditions, that is, when the following conditions are true, it will then be judged and will not fail to return directly:

  • Thread Pool in normal operation
  • Task thread pool is in a closed state, is empty Judai added, and the task queue is not empty (this case will be the processing queued tasks)

to sum up

Such principle of the whole thread pool to finish, and I was not ready in the concluding portion repeat the process, because the process has been summed up in the concept are part of the above, I am here to say it points to note:

  • If you do not manually pass the thread factory, the thread pool will provide a default thread factory, rather than simply new Thread (Runnable) creates a thread
  • After calling thread pool shutdown () method, you must remember the thread pool does not close immediately, but will then handle the task queue
  • ... and so on later thought to add

Guess you like

Origin juejin.im/post/5cef621a51882520724c7c01