Interview questions - an article about Java thread pool is enough

In Java interview, the thread pool knowledge, though not mentioning that must be asked, but the emergence of the frequency is very high. In view of the public while number "New Horizons program," the message so that the reader back to write an article about Java thread pool, so there is this part Part herein, this will be based on the principles of Java thread pool to achieve and explain the relevant source code and so on.

What is the thread pool

Thread pool is a multithreaded processing forms, will be submitted to the task processing thread pool thread pool to perform tasks handed over to be managed.

In order to take full advantage of the multi-core CPU resources, the application will use multi-threaded parallel / concurrent computation, maximize the use of multicore application performance.

Imagine if each request is executed again to create a thread to perform tasks, destroying threads, then the server will be a waste of resources. In the case of high concurrency, or even run out of server resources.

The main role of the thread pool is two-fold: duplication between different requests the use of threads, without frequent thread creation and destruction, reduce overhead and limit the number of threads of control, avoid creating too many threads run out of process memory space, while reducing thread context switching times.

Common interview questions

  • Talk about the benefits of Java thread pool and realization of the principle?
  • Java provides all the operands thread pool, how to proceed?
  • According to an internal thread pool mechanism, when submitting a new task, which should be considered abnormal?
  • What kinds of thread pool has a work queue?
  • Use unbounded queue thread pool can cause memory soar it?
  • Talk about some common thread pool and use scenarios?

Creating and using a thread pool

Increase in built-in version JDK5 thread pool implementation ThreadPoolExecutor, while providing Executors to create different types of thread pool. Executors are provided in the following common thread pool creation methods:

  • newSingleThreadExecutor: a single-threaded thread pool. If the result of abnormal end, would then create a new one, to ensure the execution order of submission.
  • newFixedThreadPool: create a thread pool of fixed size. Increments the thread according to the task submitted, up to a maximum remains unchanged. If the result of abnormal end, will create a new thread supplement.
  • newCachedThreadPool: Creating a cached thread pool. Automatically add or recycled thread according to the task.
  • newScheduledThreadPool: Support for regular and periodic requirements to perform tasks.
  • newWorkStealingPool: JDK8 add, to dynamically create and off according to the desired thread level parallelism by using multiple queues reduce competition, the use of the underlying ForkJoinPool achieved. The advantage that the CPU can take advantage of multiple, to split the task into multiple "small tasks", into a plurality of parallel execution on a processor core; when a plurality of "small tasks' execution completion, and then merge the results of these It can be.

While providing Executors class in the JDK to support more than one type of thread pool is created, but usually not recommended for developers to directly use (see "Ali Baba java development specifications").

Executors thread pool are not allowed to create, but by ThreadPoolExecutor way, this approach allows the students to write more explicit operating rules thread pool, to avoid the risk of resource depletion.

Part of the method of Executors drawbacks:

  • newFixedThreadPool and newSingleThreadExecutor main problem is the accumulation of request processing queue may consume very large memory, even OOM.
  • newCachedThreadPool and newScheduledThreadPool: The main problem is the maximum number of threads is Integer.MAX_VALUE, may create a very large number of threads, even OOM.

Meanwhile, Alibaba java development standard recommended by the three kinds of thread pool created.

One embodiment, the introduction of commons-lang3 package.

//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());复制代码

Mode 2 is introduced com.google.guava package.

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
    .setNameFormat("demo-pool-%d").build();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown复制代码

Three ways, spring thread pool configuration mode: custom thread bean plants need to achieve a ThreadFactory, reference may be other default implementation class interface, use direct injection bean, calls execute (Runnable task) method may be.

<bean id="userThreadPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="10" />
    <property name="maxPoolSize" value="100" />
    <property name="queueCapacity" value="2000" />

<property name="threadFactory" value= threadFactory />
    <property name="rejectedExecutionHandler">
        <ref local="rejectedExecutionHandler" />
    </property>
</bean>
// in code
userThreadPool.execute(thread);复制代码

ThreadPoolExecutor constructor

In addition to the above recommended method to create a thread pool, but also through the constructor ThreadPoolExecutor directly create a thread pool. In essence, the above method ultimately created ThreadPoolExecutor object, and then stacked packaging process.

ThreadPoolExecutor provides several constructors, constructor we call eventually be explained.

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

The core role of analytical parameters as follows:

  • corePoolSize: Core maximum number of threads in the thread pool.
  • maximumPoolSize: The maximum number of threads the thread pool size.
  • keepAliveTime: thread pool size of non-viable core thread idle time.
  • unit: idle thread survival time units.
  • workQueue: storing blocking queue tasks.
  • threadFactory: Create a new thread factory, all threads are created by the plant, there is a default implementation.
  • handler: deny policy thread pool.

Cheng pool denial policy

The last parameter RejectedExecutionHandler constructor for rejecting a policy specifies the thread pool. When the request came ongoing task, and the system at this time and to handle, however, we need to take the corresponding strategy is denial of service.

By default there are four types:

  • AbortPolicy Strategy: The strategy will direct throw, prevent the system from working properly.
  • CallerRunsPolicy strategy: as long as the thread pool is not closed, the policy directly in the caller's thread, run the current task is discarded.
  • DiscardOleddestPolicy strategy: This strategy will discard the oldest one request, that is the task to be executed, and try to submit the current job again.
  • DiscardPolicy strategy: the strategy silently discard task can not be processed without any treatment.

Of course, in addition to the default four kinds of strategies can also customize reject policies based on business needs. By implementing RejectedExecutionHandler interface when you create an object as a parameter to ThreadPoolExecutor.

In the spring-integration-core will be customized CallerBlocksPolicy, the relevant code is as follows:

public class CallerBlocksPolicy implements RejectedExecutionHandler {
    private static final Log logger = LogFactory.getLog(CallerBlocksPolicy.class);
    private final long maxWait;

    public CallerBlocksPolicy(long maxWait) {
        this.maxWait = maxWait;
    }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (!executor.isShutdown()) {
            try {
                BlockingQueue<Runnable> queue = executor.getQueue();
                if (logger.isDebugEnabled()) {
                    logger.debug("Attempting to queue task execution for " + this.maxWait + " milliseconds");
                }

                if (!queue.offer(r, this.maxWait, TimeUnit.MILLISECONDS)) {
                    throw new RejectedExecutionException("Max wait time expired to queue task");
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Task execution queued");
                    }

                }
            } catch (InterruptedException var4) {
                Thread.currentThread().interrupt();
                throw new RejectedExecutionException("Interrupted", var4);
            }
        } else {
            throw new RejectedExecutionException("Executor has been shut down");
        }
    }
}复制代码

Execution thread pool

Once created ThreadPoolExecutor, when you submit the task to the thread pool, usually use the execute method. Execute method flowchart is executed as follows:

image

  • If the number of kernel threads in the thread pool survival is less than the number of threads corePoolSize, will create a core thread pool thread to handle the task submitted.
  • If the core number of threads in the thread pool is full, that is equal to the number of threads have been corePoolSize, a new task submitted, it will be put into the task queue workQueue queued for execution.
  • When the number of threads in the thread pool which has survived the corePoolSize equal, and the task queue workQueue was full, the number of threads to determine whether maximumPoolSize, whether that is the maximum number of threads is full, if not reach, create a thread to perform non-core tasks submitted.
  • If the current number of threads reached maximumPoolSize, as well as new tasks come, then refused to direct the use of policy processing.

Source code analysis

The following look at JDK8 in ThreadPoolExecutor execute method in the source code to achieve:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // 线程池本身的状态跟worker数量使用同一个变量ctl来维护
    int c = ctl.get();
    // 通过位运算得出当然线程池中的worker数量与构造参数corePoolSize进行比较
    if (workerCountOf(c) < corePoolSize) {
        // 如果小于corePoolSize,则直接新增一个worker,并把当然用户提交的任务command作为参数,如果成功则返回。
        if (addWorker(command, true))
            return;
        // 如果失败,则获取最新的线程池数据
        c = ctl.get();
    }
    // 如果线程池仍在运行,则把任务放到阻塞队列中等待执行。
    if (isRunning(c) && workQueue.offer(command)) {
        // 这里的recheck思路是为了处理并发问题
        int recheck = ctl.get();
        // 当任务成功放入队列时,如果recheck发现线程池已经不再运行了则从队列中把任务删除
        if (! isRunning(recheck) && remove(command))
            //删除成功以后,会调用构造参数传入的拒绝策略。
            reject(command);
         // 如果worker的数量为0(此时队列中可能有任务没有执行),则新建一个worker(由于此时新建woker的目的是执行队列中堆积的任务,
         // 因此入参没有执行任务,详细逻辑后面会详细分析addWorker方法)。
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果前面的新增woker,放入队列都失败,则会继续新增worker,此时线程池的状态是woker数量达到corePoolSize,阻塞队列任务已满
    // 只能基于maximumPoolSize参数新建woker
    else if (!addWorker(command, false))
        // 如果基于maximumPoolSize新建woker失败,此时是线程池中线程数已达到上限,队列已满,则调用构造参数中传入的拒绝策略
        reject(command);
}复制代码

Look addWorker following method call in the above code and parsing the source code to achieve:

private boolean addWorker(Runnable firstTask, boolean core) {
    // 这里有一段基于CAS+死循环实现的关于线程池状态,线程数量的校验与更新逻辑就先忽略了,重点看主流程。
    //...

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
         // 把指定任务作为参数新建一个worker线程
        w = new Worker(firstTask);
        // 这里是重点w.thread是通过线程池构造函数参数threadFactory生成的woker对象
        // 也就是说这个变量t就是代表woker线程。绝对不是用户提交的线程任务firstTask。
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 加锁之后仍旧是判断线程池状态等一些校验逻辑。
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 把新建的woker线程放入集合保存,这里使用的是HashSet
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 然后启动woker线程
                 // 该变量t代表woker线程,会调用woker的run方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            // 如果woker启动失败,则进行一些善后工作,比如说修改当前woker数量等
            addWorkerFailed(w);
    }
    return workerStarted;
}复制代码

addWorker method is mainly to do is create a new thread Woker, woker added to the collection. In the above method calls to the run method of the Worker class, and eventually executed runWorker method.

// Woker类实现了Runnable接口
public void run() {
    runWorker(this);
}

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    // task就是Woker构造函数入参指定的任务,即用户提交的任务
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        //一般情况下,task都不会为空(特殊情况上面注释中也说明了),因此会直接进入循环体中
        //这里getTask方法是要重点说明的,它的实现跟我们构造参数设置存活时间有关
        //我们都知道构造参数设置的时间代表了线程池中的线程,即woker线程的存活时间,如果到期则回收woker线程,这个逻辑的实现就在getTask中。
        //来不及执行的任务,线程池会放入一个阻塞队列,getTask方法就是去阻塞队列中取任务,用户设置的存活时间,就是
        //从这个阻塞队列中取任务等待的最大时间,如果getTask返回null,意思就是woker等待了指定时间仍然没有
        //取到任务,此时就会跳过循环体,进入woker线程的销毁逻辑。
        while (task != null || (task = getTask()) != null) {
            w.lock();
            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 {
                //这里设为null,也就是循环体再执行的时候会调用getTask方法
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //当指定任务执行完成,阻塞队列中也取不到可执行任务时,会进入这里,做一些善后工作,比如在corePoolSize跟maximumPoolSize之间的woker会进行回收
        processWorkerExit(w, completedAbruptly);
    }
}复制代码

Task execution process woker thread is assigned to the First perform initialization, will try to get an executable task from blocking the queue after the execution is complete, if still no task can be executed within the specified time, then enter the destruction of logic. Here only recovered corePoolSize and maximumPoolSize direct that part woker.

Execute and submit the difference

In addition you can use to perform tasks execute method can also use the submit method. The main difference is: execute do not need to apply to the return value of a scene of interest, submit method is applicable to need to focus on the return value of the scene.

Exception Handling

When performing tasks exception occurs, then the how to deal with it? First look at how to deal with when Thread thread exception.

At task by try ... catch exception is captured and processed, the following code:

Thread t = new Thread(() -> {
    try {
        System.out.println(1 / 0);
    } catch (Exception e) {
        LOGGER.error(e.getMessage(), e);
    }
});
t.start();复制代码

If many threads the default task exception handling mechanism is the same, you can set the default thread exception handling mechanism through UncaughtExceptionHandler Thread class.

UncaughtExceptionHandler implement interfaces, and calls Thread # setUncaughtExceptionHandler (UncaughtExceptionHandler) method. If you want to set the global default exception handling mechanism, you can call the Thread # setDefaultUncaughtExceptionHandler (UncaughtExceptionHandler) method.

ThreadGroup provides a default exception handling mechanism is as follows:

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}复制代码

ThreadPoolExecutor exception handling mechanism Thread is the same. Meanwhile, ThreadPoolExecutor provided a method of setting uncaughtExceptionHandler exception handling. The following example:

public class ThreadPool {

    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("demo-pool-%d")
                .setUncaughtExceptionHandler(new LogUncaughtExceptionHandler())
                .build();

        ExecutorService pool = new ThreadPoolExecutor(5, 200,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

        pool.execute(() -> {
            throw new RuntimeException("测试异常");
        });

        pool.shutdown();
    }

    static class  LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("打印LogUncaughtExceptionHandler中获得的异常信息:" + e.getMessage());
        }
    }
}复制代码

But it should be noted that the use of UncaughtExceptionHandler method applies only to execute the task execution method, and the method is to submit invalid. Submit task execution may be received by a thrown exception get returned Future object method, and then processed. This method can be considered one of the differences execute and submit method.

Common thread pool queue

Work queue thread pool has the following:

  • ArrayBlockingQueue: bounded queue, use an array to achieve bounded blocking queue, a FIFO ordering quantity.
  • LinkedBlockingQueue: queue capacity can be set, based on the blocking list queue structure, a FIFO sorting task, the capacity may be set to select, if not set, the queue is a non-blocking boundary, the maximum length of Integer.MAX_VALUE, higher throughput usually in ArrayBlockingQuene; newFixedThreadPool uses this thread pool queue.
  • DelayQueue: delay queue, the queue is a task execution timing of the delay period. According to the specified time from small to large order execution, or has ordering queue according to the insert. newScheduledThreadPool uses this thread pool queue.
  • PriorityBlockingQueue: priority queue is unbounded blocking queue with priority.
  • SynchronousQueue: synchronous queue does not store a blocking queue elements, each insert operation must wait until another thread calls the removal operation, or insert operation has been in a blocked state, throughput is usually higher than LinkedBlockingQuene, newCachedThreadPool uses this thread pool queue .

Close the thread pool

Close the thread pool can call shutdownNow and two methods shutdown to achieve.

shutdownNow: on the task being performed all the issued interrupt (), stop execution of the task has not yet started execution of the abolition of all, and not return to the task list to start.

shutdown: When we call the shutdown, the thread pool will no longer accept new tasks, but will not be forced to terminate has been submitted or task being performed.

Reference article:

https://www.jianshu.com/p/5df6e38e4362

https://juejin.im/post/5d1882b1f265da1ba84aa676

Original link: " interview questions - an article about Java thread pool is enough ."


New Horizons program : exciting and growth are not to be missed

New Horizons program - micro-channel public number

Guess you like

Origin juejin.im/post/5df7864ce51d455821125b07