14
头图

This is why's 98th original article

A few days ago, a friend looked for me on WeChat. He asked: Why brother, are you there?

I said: What happened to the kidney?

He asked a question with a snap, soon.

I was careless and took a casual look. Isn't this question very simple?

It turned out that there was still an article hidden inside.

The story starts with this question:

The thread pool configuration in the above figure is like this:

ExecutorService executorService = new ThreadPoolExecutor(40, 80, 1, TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(100), 
                new DefaultThreadFactory("test"),
                new ThreadPoolExecutor.DiscardPolicy());

I will not explain the parameters and execution flow of the thread pool above.

After all, I used to "One Person's Blood Book" at 16076b626d55f9, and I want to ask why brother to talk about this interview question. "16076b626d55fd has sworn a

The above question is actually a very simple eight-legged essay question:

When are non-core threads recycled?

If after the keepAliveTime time has elapsed, threads exceeding the number of core threads have not received new tasks, they will be recycled.

The standard answer is completely fine.

So I will now introduce a simple scenario. For simplicity and intuitiveness, let's adjust the parameters related to the thread pool:

ExecutorService executorService = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2), 
                new DefaultThreadFactory("test"),
                new ThreadPoolExecutor.DiscardPolicy());

So here comes the question:

  • Is the maximum 5 tasks that this thread can hold?
  • Assuming that the task needs to be executed for 1 second, then I directly submit 5 tasks to the thread pool in a loop, and the submission must be completed within 1 second, so are there 3 active threads in the current thread pool?
  • If there are no tasks submitted for the next 30 seconds. So after 30 seconds, are there 2 active threads in the current thread pool?

The answers to the above three questions are all yes. If you don't understand why, then I suggest you to quickly supplement the knowledge points related to the thread pool. The following content must be stunned if you force it to read it.

The next question is this:

  • If there are 3 active threads in the current thread pool (2 core threads + 1 non-core thread), but their respective tasks have been executed and they are all in the waiting state. Then I throw a task that takes 1 second into the thread pool every 3 seconds. So after 30 seconds, what is the number of active threads?

Let me talk about the answer first: still three.

From my personal normal thinking, it is this: the core thread is idle, and a task that takes 1 second is thrown every 3 seconds, so only one core thread is required to completely process it.

Then, within 30 seconds, the thread that exceeds the core thread has been in a waiting state, so after 30 seconds, it is recycled.
But the above is just my subjective opinion, but what about the actual situation?

After 30 seconds, threads that exceed the core thread​ will not be recycled, and there are still 3 active threads.
At this point, if you know that there are 3, and you know why there are 3, that is, to understand why the non-core threads are not recycled, then the following content should be what you have already mastered.

Don't watch it, pull it to the end, click a like, and go to your own business.

If you don’t know, you can continue to see why there are three.

Although I believe no interviewer will ask such a question, it is helpful for you to understand the thread pool.

Demo first

Based on the scenario I mentioned earlier, the code is as follows:

public class ThreadTest {

    @Test
    public void test() throws InterruptedException {

        ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2), new DefaultThreadFactory("test"),
                new ThreadPoolExecutor.DiscardPolicy());
                
        //每隔两秒打印线程池的信息
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("=====================================thread-pool-info:" + new Date() + "=====================================");
            System.out.println("CorePoolSize:" + executorService.getCorePoolSize());
            System.out.println("PoolSize:" + executorService.getPoolSize());
            System.out.println("ActiveCount:" + executorService.getActiveCount());
            System.out.println("KeepAliveTime:" + executorService.getKeepAliveTime(TimeUnit.SECONDS));
            System.out.println("QueueSize:" + executorService.getQueue().size());
        }, 0, 2, TimeUnit.SECONDS);

        try {
            //同时提交5个任务,模拟达到最大线程数
            for (int i = 0; i < 5; i++) {
                executorService.execute(new Task());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //休眠10秒,打印日志,观察线程池状态
        Thread.sleep(10000);

        //每隔3秒提交一个任务
        while (true) {
            Thread.sleep(3000);
            executorService.submit(new Task());
        }
    }

    static class Task implements Runnable {
        @Override
        public void run(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "-执行任务");
        }
    }
}

This code was also given to me by the guy who asked the question. I made some fine-tuning, and you can just stick it out and run it.

show me code, no bb. This is the correct posture for mutual discussion.

The running result of this program is like this:

There are five tasks in total. What is the running situation of the thread pool?

First look at the place marked ①:

The three threads are all performing tasks, and then the 2nd thread and the 1st thread complete the task first, and then take out the two tasks in the queue for execution (labeled ②).

According to the procedure, there will be a task that takes 1 second every 3 seconds. At this time, the three active threads in the thread pool are all idle.

Then the question comes:

Which thread should be selected to perform this task? Did you pick one at random?

Although the next program has not been executed, based on the previous screenshot, I can tell you now that the next task, the thread execution sequence is:

  • Thread[test-1-3,5,main]-execute task
  • Thread[test-1-2,5,main]-execute task
  • Thread[test-1-1,5,main]-execute task
  • Thread[test-1-3,5,main]-execute task
  • Thread[test-1-2,5,main]-execute task
  • Thread[test-1-1,5,main]-execute task
  • ......

That is, although the threads are idle, they are not called randomly when the task comes, but polled.

Because it is polling, it is executed every three seconds, so the idle time of non-core threads is 9 seconds at most, not more than 30 seconds, so they will never be recycled.

Based on this Demo, we have answered from the surface, why the number of active threads is always 3.

Why polling?

We verified through Demo that in the above scenario, the thread execution order is polling.

So why?

This is just the appearance from the log. What about the internal principle? What about the corresponding code?

This section takes everyone to see what is going on.

First of all, when I saw this appearance, I guessed: these three threads must be stored in a certain queue somewhere, and based on this, the polling call can be realized.

So, I have been looking for this queue, but have not found the corresponding code, I am still a little anxious. Think it is not controlled at the operating system level, right?

Later, I calmed down and thought it was impossible. So between the flashing lights, I thought, or else dump the threads first to see what they are doing:

After dumping, this thing is familiar to me, the waiting queue of AQS.

According to the stack information, we can locate the source code here:

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#awaitNanos

When I saw this, I suddenly realized it.

The harm is that I think too much.

To put it bluntly, this is actually a producer-consumer issue.

Three threads are three consumers. Now there are no tasks to be processed. They just wait for the producers to produce tasks, and then notify them that they are ready to consume.

Since this article only takes you to find where the answer is in the source code, it does not interpret the source code.

So I assume that you have a certain understanding of AQS.

You can see that the addConditionWaiter method is actually operating the queue we are looking for. The scientific name is called waiting queue.

Debug and look at the situation in the queue:

Coincidentally, this is not it. The sequence happens to be:

  • Thread[test-1-3,5,main]
  • Thread[test-1-2,5,main]
  • Thread[test-1-1,5,main]

On the consumer side, we probably figured it out, and then we went to look at the producer.

  • java.util.concurrent.ThreadPoolExecutor#execute

The thread pool is here to put tasks into the queue.

And the source code in this method is like this:

Among them, signalNotEmpty() will eventually go to the doSignal method, which will call the transferForSignal method.

This method will call the LockSupport.unpark(node.thred) method to wake up the thread:

And the order of wake-up is the order in the waiting queue:

So, now you know when a task comes, which thread in the thread pool should execute the task, this is not random, nor is it random.

It is about order.

In what order?

The order in the waiting queue in Condition.

What, you don’t understand Condition?

Why don't you hurry up to learn? Waiting for me to tell you?

Originally I wanted to write about it, but later found out that section 5.6.2 in the book "The Art of Concurrent Programming in Java" has been written quite clearly, with illustrations and texts. This part of the content is actually a high-frequency test site during the interview, so just go and see it yourself.

First owe, owe.

How to recycle non-core threads?

Or the above example, assuming that the non-core thread is idle for more than 30 seconds, then how is it recycled?

This is also a popular interview question.

There is nothing advanced about this question, the answer is hidden in this place in the source code:

  • java.util.concurrent.ThreadPoolExecutor#getTask

When the timed parameter is true, the workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) method will be executed.

And when is timed true?

  • boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

allowCoreThreadTimeOut defaults to false.

So, just look at the wc > corePoolSize , wc is the number of active threads. At this time, the number of active threads is 3, which is greater than the number of core threads 2.

Therefore timed is true.

In other words, when the current workQueue is empty, all three threads are now blocked in the workQueue.poll method.

And when the workQueue is still empty after the specified time, it returns null.

So change timeOut to true on line 1077.

Enter the next cycle and return null.

This method will eventually be executed:

  • java.util.concurrent.ThreadPoolExecutor#processWorkerExit

And this method will perform the remove operation.

So the thread is recycled.

So when the specified time is exceeded, the thread will be recycled.

So is the recycled thread a core thread or a non-core thread?

do not know.

Because in the thread pool, core threads and non-core threads are just a concept, in fact, holding a thread, we can not know whether it is a core thread or a non-core thread.

This place is a proof, because when the number of worker threads exceeds the number of core threads, all threads are polled, which means that all threads may be recycled:

Another strong proof is addWorker here:

The core parameter only controls whether to take corePoolSize or maximumPoolSize.

So, how do you answer this question:

The way JDK distinguishes is not to distinguish.

So can we know?

Yes, for example, by observing the log, in the previous case, I knew that these two are core threads, because they were created first:

  • Thread[test-1-1,5,main]-execute task
  • Thread[test-1-2,5,main]-execute task

How do you know in the program?

I don't know at present, but this demand can be realized by adding money.

Expand the thread pool yourself. Isn't it simple to mark the threads in the thread pool?

Just think about it, what do you do to distinguish this stuff, is there a demand that can be implemented?

After all, talk about fulfillment out of demand. They are all hooligans.

One last word

If you find something wrong, you can bring it up in the background and I will modify it.

Thank you for reading, I insist on originality, very welcome and thank you for your attention.


why技术
2.2k 声望6.8k 粉丝