Java implementation principle understanding ThreadPoolExecutor

The advantage of using a thread pool (ThreadPoolExecutor) is to reduce the overhead of creating and destroying threads on the time spent and system resources, to solve the problem of insufficient resources. If you do not use the thread pool, it may cause the system to create a large number of similar threads lead consumed or memory problem "excessive handover". - Ali Java Development Manual

Directly to the theme

version

JDK 1.8

This section target

  • Understand the core thread pool parameters
  • Understanding the thread pool works
  • Understand the core thread pool method

The core thread pool parameters and construction methods

ctl
    // 线程池核心变量,包含线程池的运行状态和有效线程数,利用二进制的位掩码实现
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    // 线程池状态
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    // 获取当前线程池运行状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 获取当前线程池有效线程数
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 打包ctl变量
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    /*
     * Bit field accessors that don't require unpacking ctl.
     * These depend on the bit layout and on workerCount being never negative.
     */

    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

After JDK7, state and effective thread pool threads by ctl (using the binary bit mask to achieve, we do not go into here), understand the above several methods to effect this variable does not affect the following source code reading

Five states on the thread pool
  • RUNNING: accept new tasks and processing tasks in the queue

  • SHUTDOWN: do not accept the new task, but the task queue processing

  • STOP: do not accept the new task, not the task queue processing, and interrupt an ongoing task (interrupt not mandatory, just modify the state of Thread, depending on whether the interrupt logic to achieve the Runnable)

  • TIDYING: all tasks have been terminated, workerCount is 0, the thread pool will be over to the state, and is about to call terminate ()

  • TERMINATED: terminated () call is completed; abort the thread pool

Conversion thread pool status
  • RUNNING => SHUTDOWN :调用 shutdown()

  • RUNNING / SHUTDOWN => STOP: shutdownNow call () (which returns unexecuted task queue)

  • SHUTDOWN => TIDYING: when the thread pool is empty and the queue

  • STOP => TIDYING: When the thread pool is empty

  • TIDYING => TERMINATED: When terminated () call is completed

Construction method

Thread pool are ultimately call the constructor as follows

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
  // 省略
}
Core parameters

Let's look at the core parameters in the thread pool is what role

private final BlockingQueue<Runnable> workQueue; // 阻塞队列,用于缓存任务

private final ReentrantLock mainLock = new ReentrantLock(); // 线程池主锁

private final HashSet<Worker> workers = new HashSet<Worker>(); // 工作线程集合

private final Condition termination = mainLock.newCondition(); // awaitTermination() 方法的等待条件

private int largestPoolSize; // 记录最大线程池大小

private long completedTaskCount; //用来记录线程池中曾经出现过的最大线程数

private volatile ThreadFactory threadFactory; // 线程工厂,用于创建线程

private volatile RejectedExecutionHandler handler; // 任务拒绝时的策略

private volatile long keepAliveTime; // 线程存活时间
                                     // 当线程数超过核心池数时,或允许核心池线程超时,该参数会起作用。否则一直会等待新的任务

private volatile boolean allowCoreThreadTimeOut; // 是否允许核心池线程超时

private volatile int corePoolSize; // 核心线程池数量

private volatile int maximumPoolSize; // 最大线程池数量

About handler

  • ThreadPoolExecutor.AbortPolicy: RejectedExecutionException discard task and throw an exception.
  • ThreadPoolExecutor.DiscardPolicy: the task is discarded, but does not throw an exception.
  • ThreadPoolExecutor.DiscardOldestPolicy: discarding foremost task queue, and then try to re-execute task (repeat this procedure)
  • ThreadPoolExecutor.CallerRunsPolicy: the current task to decide

Let's take chestnuts to better understand what the thread pool

Understanding the thread pool works

If there is a factory, the factory which has 10 workers, each worker can only do one task at the same time.

So as long as 10 workers when workers are free to the task assigned to idle workers do;

When 10 workers have tasks to do, if it is to the task, put the task waiting;

Each worker after finishing their task, the task will go to the queue to receive new tasks;

If the number of new job growth is much faster than the speed of the workers to do the task (task accumulate too much), then the time plant supervisor might want to remedial measures, such as re-recruit four temporary workers to come in;

Then the task will be assigned to the four temporary workers to do;

If you said 14 workers to do the task speed is not enough, then plant manager may have to consider not taking any new tasks or to abandon some of the previous task.

When these 14 workers, which was idle, and the new tasks and relatively slow growth rate, plant manager might consider quitting four temporary workers, just to keep the original 10 workers, after all, is to make extra money workers of.


  • 10 factory workers began, is corePoolSize (number of cores pool);

  • When 10 people are at work (core pool reaches corePoolSize), tasks are queued, cached to workQueue in;

  • When the task accumulate too much (workQueue reached the maximum value), looking for temporary workers;

  • 14 temporary workers, is maximumPoolSize (number);

  • If the pace of work at this time is not enough, then the thread pool will refuse to consider the task specific policy decisions by the refusal

Understand the core thread pool method

execute()

Methods All methods to perform tasks related to the thread pool will call the execute (). If you understand the above-mentioned small example, look at this lot will be clear

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}
Analysis execute ()
  • step 1

1) First, check the current number of active threads is less than the core pool number
if (workerCountOf(c) < corePoolSize)

2) If the above conditions are met, attempting to add a worker thread (addWorker () to the core pool of second parameter determines the core tank is added, or the largest pool)
if (addWorker(command, true))

3) If successful exit method, otherwise it will perform step2

  • step 2

1) if the current thread pool running && try to add tasks to the queue buffer
if (isRunning(c) && workQueue.offer(command))

2) If the thread pool is running and the buffer queue to add a task successfully perform double check (check again)

3) If at this time the non-operating state of thread pool => Remove Queue => Reject the current task, the method exits
(This is done in order, when the thread pool is not available in time rollback)

if (! isRunning(recheck) && remove(command))
    reject(command);

4) 0, no task is to create a worker thread (the thread at this time will go to get the task queue) to ensure that the task of submitting to the thread pool if the number of currently active threads must be executed

  • step 3

1) When you can not can not be added to the core task of the pool and the queue, the thread pool will try to add a worker thread pool maximum, if it fails then reject the task

else if (!addWorker(command, false))
         reject(command);
Illustrates execute ()

According to the following steps drew this map, hoping to help you better understand

9949918-5c6d49879e02afdd.png
image.png
addWorker()

In analyzing the execute () method, we already know the role addWorker () is, you can add a pool of worker threads to the core or the maximum pool. Let's look at this approach have done what

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

This method code appears to be very complicated, it does not matter, we step by step analysis

  • 1 step
    look at the first portion
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

This part of the code, mainly to determine, whether a worker can be added .

In execute () has been judged if (workerCountOf(c) < corePoolSize), why then judge?

Because in a multithreaded environment, when a context switch to here, may have closed the thread pool, or other thread submit the task, resulting inworkerCountOf(c) > corePoolSize

1) First, enter the first for an infinite loop, get ctl object to obtain the operating state of the current thread, then judge

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

In this sense judgment is, when the thread pool running state> = SHUTDOWN, must meet the same time rs == SHUTDOWN, firstTask == null, ! workQueue.isEmpty()the three conditions, if there is one condition is not met, ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())it will be to true, resulting in return false; adding threads fail

So when the thread status is SHUTDOWN, and the queue when another task, you can add a thread pool worker thread no task to perform tasks in the queue.

2) for an infinite loop into the second

for (;;) {
    int wc = workerCountOf(c);
    if (wc >= CAPACITY ||
        wc >= (core ? corePoolSize : maximumPoolSize))
        return false;
    if (compareAndIncrementWorkerCount(c))
        break retry;
    c = ctl.get();  // Re-read ctl
    if (runStateOf(c) != rs)
        continue retry;
    // else CAS failed due to workerCount change; retry inner loop
}

Gets the current number of active threads, if the effective number of threads > = capacity || effective number of threads > = number of cores pool / maximum number of pool , then return false; adding threads fail

If the number of active threads within a reasonable range, try to use the CAS increment effective number of threads (CAS is optimistic locking in Java, you do not understand the small partners can Google it)

If the increment successful, break retry; out of these two cycles, the following code is executed

Increment fails, check the status of the thread pool, thread pool if the state changes back to the first for continues; otherwise continue in the second for the;

  • 2 step
    following section is relatively simple
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Recheck while holding lock.
            // Back out on ThreadFactory failure or if
            // shut down before lock acquired.
            int rs = runStateOf(ctl.get());

            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive()) // precheck that t is startable
                    throw new IllegalThreadStateException();
                workers.add(w);
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}
return workerStarted;

1) Create a worker thread objects Worker;

2) lock, determine whether the current state of thread pool thread is allowed to start;
if you can join the thread workerswill be used when (the variables need to traverse all worker threads), records the maximum, start the thread;

3) If the thread failed to start, execute addWorkerFailed (from workersremoval of the object, the number of active threads minus one, attempting to abort a thread pool)

Worker

Worker object is an inner class thread pool, thread multiplexing, in this thread timeout is implemented

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker implements Runnable, we only care about the run method Worker's doing what on paper is not relevant to the discussion AbstractQueuedSynchronizer

    public void run() {
        runWorker(this);
    }
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

runWork is relatively simple

  • while loop continuously () Gets task from the task queue by the getTask;

  • When the thread-pool STOP, the thread will be interrupted;

  • Then is executed task.run, that is, we execute Runnable defined;

  • If our Runnable or throw an exception getTask() == nullwhen (that is, buffer queue is empty), will perform processWorkerExit(w, completedAbruptly);(which will be based on the thread pool state, attempting to abort the thread pool. Then consider it is the end of the current thread, or re-create a worker thread here not elaborate)

  • beforeExecute (wt, task); and afterExecute (task, thrown); default is not achieved, we can extend yourself

Let us look at getTask () method

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

1) if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()))When this condition is satisfied decrement the number of effective threads and return null; when return null, runWorker the will to implement the saidprocessWorkerExit(w, completedAbruptly);

2) timed: whether to allow the thread timeout ;
if allowed to pool core thread timeout || effective thread count> number of cores pool time, timed = true;
by the way timedOut: if the thread timeout , when timed = true when, timedOut possible to true

3)

// (有效线程数 > 最大线程池数量 || (允许超时 && 超时) ) 
//  && (有效线程数 > 1 || 或者队列为空时)
if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;
    continue;
}

Satisfy the above conditions, and effectively reducing the number of threads since successful, return null;

4) According to timedthe decision to call using the poll () or take (). poll in the queue is empty will wait for the specified time, take it waits in the queue is empty until they are adding a new task queue, or be interrupted;

Both methods are shutdown () or shutdownNow 的 thread.interrupt()interrupted;
if executed again interrupted

Only when a poll waiting timeout will return null; timeOut = true, then look at step 3 (timed && timedOut) meet on

So far most of the logic execute () method we are involved in the analysis done

Remark

Use the thread pool
public class Test {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5));

        executor.execute(() -> {
            // 业务逻辑
        });

        executor.shutdown();
    }

}
The rational allocation of thread pool size

General need to configure the thread pool size depending on the type of tasks:

If CPU-intensive tasks, the reference value may be set (N is the number of core CPU) N + 1

If IO intensive task, the reference value may be set to 2 * N

Of course, this is only one reference value, the specific settings also need to be adjusted according to the actual circumstances, for example first thread pool size can be set as a reference value, and then observe the operation of the system load task, resource utilization to be adjusted appropriately.

reference

Reproduced in: https: //www.jianshu.com/p/40e3df67441e

Guess you like

Origin blog.csdn.net/weixin_33724059/article/details/91303688