preface
This is a real interview question.
A few days ago, a friend shared a question he had just asked a candidate in a group interview: “How does the thread pool execute sequentially according to the execution of core, Max and queue?” .
CorePool ->workQueue->maxPool ->maxPool
For a time in the group of fried pot, small partners have to inquire about his company, and then shield to avoid pits. (Jun jun ٩(❛ᴗ❛ danjun) : p
He asked the following questions about thread pools:
- How do thread pools execute in core, Max, queue order?
- Is the main thread aware of exceptions thrown by child threads?
- How to handle exception changes in the thread pool?
Is all some interesting question, before I also wrote a very detailed graphic tutorials: | learn word by – original 】 【 in Java thread pool, this article may be enough! If you don’t know, you can review it again
But in view of these several questions, we may also have a little ignorant for a time. Today’s article looks at how to answer these three questions, using source code as a basis. (It doesn’t matter if you haven’t read the source code before, all analysis will post the source code and diagrams)
How does the thread pool execute in core, Max, queue order?
thinking
To this question, many of you may wonder: “Why would you change the execution flow written in someone else’s source code? This interviewer is out of his mind……”
Let’s think about whether there is a need for this in a real work situation. I’ve seen a resume that asks this question before:
Tasks performed by a thread pool are IO intensive, most of the cpus are idle, and system resources are not fully utilized. If a large number of requests come in all at once, if the thread pool is larger than coreSize, the extra requests will be put into the wait queue. Wait for threads in the corePool to complete execution before executing tasks in the wait queue.
How can we optimize this scenario?
We can change the thread pool execution order to corePool->maxPool->workQueue. In this way, CPU resources are fully utilized and submitted tasks are executed first. Tasks are queued only when the number of threads in the thread pool exceeds maxSize.
Do you think it’s a coincidence? This is an interesting question that the interviewer has clearly thought through, so let’s take a look at how to solve it.
Thread pools run processes
We all know that thread pool execution flow is corePool followed by workQueue, and maxPool execution flow is the last.
Thread pool core parameters
ThreadPoolExecutor under review. The execute () source before we review several important parameters in the thread pool:
CorePoolSize: specifies the number of core threads in the thread pool. MaximumPoolSize: specifies the maximum number of threads in the thread pool. KeepAliveTime: specifies the time for non-core idle threads to wait for new tasks. With allowCoreThreadTimeOut, threads in the core thread pool are also cleaned up. ThreadFactory: specifies a custom threadFactory. You can specify the name of each thread. Handler: specifies the AbortPolicy
ThreadPoolExecutor. The execute () source code analysis
We can look at execute() as follows:
Then analyze the execution process:
- The first step:
workerCountOf(c)
Time counts the number of threads in the current thread pool when the number of threads is smaller than the number of core threads - Step 2: The number of threads in the thread pool is greater than the number of core threads
workQueue
In the useoffer()
operate - Step 3:
workQueue.offer()
If the execution fails, the newly submitted task will be executed directly.addWorker()
Determines that if the current thread pool is greater than the maximum number of threads, the reject policy is executed
Ok, so we’re pretty clear at this point, but the key is how do we switch the order of step 2 and step 3?
solution
If you think about it, wouldn’t it be possible to change the implementation of workqueue.offer ()? Let’s start by drawing a picture:
The problem is that if coreSize < workCount < maxSize in the current thread pool, offer() must be performed first.
Can we change the execution order if we modify the implementation of offer? Here’s a picture to show it:
EagerThreadPool solution in Dubbo
In Dubbo’s EagerThreadPool, there is a custom BlockingQueue. In the offer() method, if the current thread pool is smaller than the maximum thread pool, the offer() method returns false, which is used to adjust the thread pool execution order.
Source direct: github.com/apache/dubb…
See here everything comes to light, the solution and the solution are very simple, have you learned?
This problem is also hidden behind some scene optimization, source code extension and so on knowledge, really is a good problem worth thinking about.
Is the main thread aware of exceptions thrown by child threads?
thinking
This question is also easy to answer and is only an interview question. Exceptions in the actual working neutron thread should not be caught by the main thread.
We want to be clear about this: we need to define the boundaries of thread code. During asynchrony, exceptions thrown by child threads should be handled by the child threads themselves, not by the main thread awareness.
The solution
Solution is very simple, in a virtual machine, when a Thread if there is no explicit exception handling and thrown when the abnormal events reported to the Thread object. Java lang. Thread. UncaughtExceptionHandler for processing, If the thread is not set to UncaughtExceptionHandler, the stack will be output to the terminal by default and the program will crash.
So if we want to do something when a thread crashes unexpectedly we can do it by implementing UncaughtExceptionHandler.
When we set ThreadFactory with the thread pool, we can specify UncaughtExceptionHandler to catch exceptions thrown by child threads.
Code sample
The specific code is as follows:
/** * Test child thread exception **@author wangmeng
* @date2020/6/13 18:08 * /
public class ThreadPoolExceptionTest {
public static void main(String[] args) throws InterruptedException {
MyHandler myHandler = new MyHandler();
ExecutorService execute = new ThreadPoolExecutor(10.10.0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 10; i++) {
execute.execute(newMyRunner()); }}private static class MyRunner implements Runnable {
@Override
public void run(a) {
int count = 0;
while (true) {
count++;
System.out.println("I'm going to start producing bugs ============");
if (count == 10) {
System.out.println(1 / 0);
}
if (count == 20) {
System.out.println("It will not be executed here ==========");
break;
}
}
}
}
}
class MyHandler implements Thread.UncaughtExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage()); }}Copy the code
Execution Result:
UncaughtExceptionHandler parsing
UncaughtExceptionHandler (Thread)
public class Thread {.../ * * * when a Thread is terminated due to an uncaught exception when the virtual machine will use the Thread, getUncaughtExceptionHandler () * get UncaughtExceptionHandler example has been set, And by calling its uncaughtException(...) Method and pass the relevant exception information. * If a thread does not explicitly set its UncaughtExceptionHandler, its ThreadGroup object is treated as its * handler. If ThreadGroup objects have no special requirements for exceptions, ThreadGroup forwards the call to the default uncaught exception handler (that is, the static uncaught exception handler object defined in the Thread class). * *@see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
*/
@FunctionalInterface
public interface UncaughtExceptionHandler {
/** * Callback this method */ if no exception crashes are caught
void uncaughtException(Thread t, Throwable e);
}
/** * static method that sets a default global exception handler. * /
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
/** * A method on a Thread object for uncaught exception handling on a particular Thread. * /
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
/** * This method is called to retrieve the current Thread's handler when the Thread crashes, or to call group (type handler) if the Thread fails to retrieve it. * Group is a ThreadGroup type attribute of the Thread class, instantiated in the Thread construct. * /
public UncaughtExceptionHandler getUncaughtExceptionHandler(a) {
returnuncaughtExceptionHandler ! =null ?
uncaughtExceptionHandler : group;
}
/** * thread global default handler. * /
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(a) {
returndefaultUncaughtExceptionHandler; }... }Copy the code
Part reference since: mp.weixin.qq.com/s/ghnNQnpou…
How to handle exception changes in the thread pool?
What if an exception occurs during thread execution in the thread pool? There are two ways to submit a thread pool task, execute() and submit(), which are explained here in turn.
ThreadPoolExecutor. RunWorker () implementation
Whether to use the execute () or submit () submit a task, will eventually perform to ThreadPoolExecutor. RunWorker (), we’ll look at the source code (source code based on JDK1.8) :
We see an exception thrown directly up when executing task.run(). The best way to handle this is to use try in our business code… Catch () to catch an exception.
FutureTask. The run () implementation
If we use the submit () to submit a task, the ThreadPoolExecutor. RunWorker will eventually call () method is executed to FutureTask. The run () method, don’t know friends can also see my previous article:
Thread pool continuation: You must know thread pool submit() implementation principle FutureTask!
As you can see, if the business code throws an exception, it is caught by a catch and calls the setExeception() method:
When we call get(), the exception information will be wrapped into the variable outcome inside FutureTask, and we will also get the corresponding exception information.
In ThreadPoolExecutor. RunWorker () the last finally have a afterExecute () hook method, if we rewrite the afterExecute () method, which can get the child thread throw specific Throwable exception information.
conclusion
The following exception handling methods are recommended for thread pools, including threads:
- Direct use of
try/catch
This is also the most recommended way - Override while we’re constructing the thread pool
uncaughtException()
Method, also mentioned in the sample code above:
public class ThreadPoolExceptionTest {
public static void main(String[] args) throws InterruptedException {
MyHandler myHandler = new MyHandler();
ExecutorService execute = new ThreadPoolExecutor(10.10.0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 10; i++) {
execute.execute(newMyRunner()); }}}class MyHandler implements Thread.UncaughtExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage()); }}Copy the code
3. Directly override afterExecute() to sense exception details
conclusion
This article ends here, do you have some feelings or harvest?
Through these several interview questions, I also deeply feel that learning knowledge to think more, look at the process of source code to set up some scenes, so that will gain more.