Sudden increase in the number of threads! Leader: Anyone who writes like this again will be fucked!

Click to follow the official account, Java dry goods will be delivered in time8fd85795d4ed0169bfb5028e57aaf415.png

c96caae7125e972d23b3beae092ab4c5.png There is no one of the strongest microservice frameworks in China!

ff216cf00aa85dba2b655d1856930773.png Covers almost all Spring Boot operations!

695a41ec329130c0a17fd65e0fad1c8b.png 2023 New Java Interview Questions (2500+)


Source: juejin.cn/post/7197424371991855159

foreword

Today, I would like to share with you a thought triggered by an online question. The process is relatively long, but it is quite interesting.

I finished writing the requirements at work today, and skywalked out of the psychology of learning (fishing), and suddenly found that one of our applications has more than 900 threads in the application, which is close to 1000, but the CPU is not high, and the memory is not counted. peak.

But being keen, I immediately realized that there was something wrong with this application, because the number of threads was too much, which did not meet our normal and healthy application number. Proficiently play the cpu dump observation, first look at the overview of the thread group name.

319cc3d54e9382aaa046a956ca6d10a0.jpeg

From the perspective of thread grouping, the number of threads at the beginning of the pool name accounts for 616, and the waiting status is also 616. This point is very suspicious. I conclude that the problem is caused by the thread pool at the beginning of the pool.

Let's first check why there are 600+ threads in the thread pool that are in the waiting state and cannot be released. Next, let's find the stacks of several threads to observe the specific stacks:

2cb8254711ad3051575362a80594d706.jpeg

This stack looks reasonable. The thread continuously obtains tasks in the thread pool. Because the task cannot be obtained, it enters the waiting state and wakes up after waiting for a task.

It seems that there is more than one thread pool, and the names of these thread pools are actually the same. I boldly guess that the same thread pool is continuously created, but the number of threads caused by the thread pool being unable to be recycled, so next we will analyze Two questions, first of all, which thread pool is this thread pool in the code, and second, how is this thread pool created? Why can't it be released?

In addition, if you are preparing for an interview to change jobs in the near future, it is recommended to brush up questions online in the Java interview library applet, covering 2000+ Java interview questions, covering almost all mainstream technical interview questions.

The results I got from searching for ideas new ThreadPoolExecutor()are as follows:

392a86f7809d740461ff03095abe97b9.jpeg

So I fell into a state of bewilderment, is there any other show operation?

At this moment, an unknown Zheng netizen sent a screenshot:

575d942c4f0659519171e690bb3c8500.jpeg

Good guy! It turned out to be used new FixedTreadPool(). No wonder I can't find it at all, because it is used new FixedTreadPool(), so the thread name in the thread pool is the default pool (another reason not to use Executors to create a thread pool).

Then I couldn't wait to die to open the code, trying to find the culprit, and found that the author was actually myself. Here's another surprise, a frightening surprise.

After calming down, I sorted out the code. I wrote this interface two years ago. Its main function is to count the monthly flow of the user's wallet. Because I was worried that the statistics would be slow, I used a thread pool to do batch processing. Unexpectedly, the number of threads was too high. Although it did not cause an accident, it was indeed a potential hidden danger. No accident now does not mean that there will be no accident in the future.

To remove redundant business logic, I simply restore a code for everyone to see, restore the scene:

private static void threadDontGcDemo(){
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.submit(() -> {
            System.out.println("111");
        });
    }

Recommend an open source and free Spring Boot practical project:

https://github.com/javastacks/spring-boot-best-practice

So why are the threads in the thread pool and the thread pool not released?

Is it because shutdown is not called? I can probably understand why I didn't call shutdown two years ago. It was because I thought that the interface was finished and the method came to an end. In theory, the stack frame should be popped and all local variables should be destroyed. It stands to reason that this variable should be directly executorServiceGG , then it stands to reason that I don't need to call the shutdown method.

I simply ran a demo, went to the new thread pool in a loop, and did not call the shutdown method to see if the thread pool could be recycled

a4cd8b3df2e5fe0f5a0e13715db761da.jpeg

Open java visual vmto view live thread:

9c961fcf6cdfd476bf5f6373ca0febd2.jpeg

It can be seen that the number of threads and the thread pool have been increasing, but they have not been recycled, which is indeed in line with the problem. So if I call the shutdown method before the method ends, will the thread pool and threads be recycled?

In addition, if you are preparing for an interview to change jobs in the near future, it is recommended to brush up questions online in the Java interview library applet, covering 2000+ Java interview questions, covering almost all mainstream technical interview questions.

Simply write a demo combined with jvisualvm verification:

84b6e118f4d7bb439dbf963ba8c04762.jpeg 320eb5c4fca61b342804ce1dc9910633.jpeg

The result is that both the thread and the thread pool are recycled. In other words, the thread pool that has executed shutdown will eventually recycle the thread pool and thread objects.

We know that whether an object can be recycled depends on whether there is a reachable path between it and the gc root. If the thread pool cannot be recycled, it means that there is still a reachable path to the gc root of the thread pool. Here is a cold knowledge, the gc root of the thread pool here is the thread, and the specific gc path is thread->workers->线程池.

The thread object is the gc root of the thread pool. If the thread object can be gc, then the thread pool object must also be gc dropped (because the thread pool object has no reachable path to the gc root).

Sharing information: 23 design patterns in practice (very complete)

So now the question turns to when the thread object is gc.

This netizen gave a superficial but reasonable explanation. The thread object must not be recycled when it is running, because it is definitely impossible for jvm to recycle a running thread, at least it is impossible for jvm to recycle a thread in runnalbe state.

On stackoverflow I found a more accurate answer:

0dce04cdbfe4982645cfc399c9f8078c.jpeg

A running thread is considered a so called garbage collection root and is one of those things keeping stuff from being garbage collected。

This sentence means that a running thread is the gc root. Note that it is running. Let me reveal that it is running, even if it is in the waiting state, it is still running. The overall meaning of this answer is that running threads are gc root, but non-running threads are not gc root (can be recycled).

Now it is clear that the key to thread pool and thread recycling is whether the thread can be recycled, so back to the original starting point, why calling the shutdown method of the thread pool can cause the thread and thread pool to be recycled? Is it the shutdown method that turns the thread into a non-running state?

talk is cheap,show me the code

Let's look directly at the source code of the shutdown method of the thread pool

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
}

private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
}

We interruptIdleWorkersstart with the method, which looks the most suspicious. Seeing interruptIdleWorkersthe method, this method mainly does one thing, traverse the threads in the current thread pool, and call interrupt()the method of the thread to notify the thread of interruption, that is to say, the shutdown method Just go through all the threads in the thread pool, and then notify the thread of interruption. So we need to understand how the threads in the thread pool handle interrupt notifications.

We click on the worker object, which is the actual running thread in the thread pool, so we directly look at the worker's run method, and the interrupt notification must be processed in it

//WOrker的run方法里面直接调用的是这个方法
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);
        }
}

This runwoker belongs to the core method of the thread pool, which is quite interesting. The principle of the continuous operation of the thread pool is here. Let's take a look at it a little bit.

First, the outermost layer is covered with a while loop, and then gettask()the method is continuously called to continuously fetch tasks from the queue. If the task cannot be obtained or an exception occurs during task execution (throwing an exception), it is an abnormal situation, and it is directly set to completedAbruptlytrue , and enter an abnormal processWorkerExitprocess.

Let's look at gettask()the method to understand when an exception may be thrown:

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;
            }
        }
    }

This is very clear. Leaving aside most of the previous code, this code explains the function of gettask:

Runnable r = timed ?
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take()

Gettask is to fetch tasks from the work queue, but there is a timed in front of it. The semantics of this timed are as follows: if the allowCoreThreadTimeOutparameter is true (usually false) or the current number of working threads exceeds the number of core threads, then use the poll method of the queue to fetch task, otherwise use the take method.

These two methods are not the point, the point is that both the poll method and the take method will make the current thread enter time_waitingor wait state. And when the thread is in the waiting state, we call the interrupt method of the thread, which will undoubtedly cause the thread to throw an exception on the spot!

shutdownnowThat is to say, the method call of the thread pool interruptIdleWorkersto interrupt the thread object is to make time_waitingthe thread in waiting or throw an exception.

So where does the thread pool handle this exception? runwokerThe method we fancy calling processWorkerExit, to be honest, this method looks like a method that handles throwing exceptions:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
}

We can see that there is an obvious workers.remove(w)method in this method, that is, here, the w variable is removed from the workers collection, so that the worker object cannot reach the gc root, so the worker object naturally becomes a Garbage object, which has been recycled.

Then wait until all the workers in the worker are removed from the works, and the current request thread is also completed, the thread pool object also becomes an orphan object, and there is no way to reach it, so the thread pool object is also dropped by gc gc root. It's been quite a while, so let me summarize:

  • The thread pool call shutdownnowmethod is to call the interrupt method of the worker object to interrupt those sleeping threads (waiting or time_waitingstatus) and make them throw exceptions

  • The thread pool will remove the reference of the worker object that throws the exception from the workers collection. At this time, the removed worker object gc rootcan be dropped by gc because there is no path to reach.

  • Wait until the workers object is empty, and the current tomcat thread ends, at this time, the thread pool object can also be dropped by gc, and the entire thread pool object is successfully released

final summary

shutdownIf you only use the thread pool in a local method, when the thread pool object is not a bean, remember to use or methods reasonably shutdownnowto release threads and thread pool objects. If not used, the thread pool and thread objects will accumulate.


course recommendation

If you want to learn Spring Boot systematically, I recommend my " Spring Boot Core Technology Course ", based on the latest 3.x version, covering almost all core knowledge points of Spring Boot , one-time payment, permanent learning...

Those who are interested scan the code to contact and subscribe to learn:

d9758b36e348c06c18ca6f733e65ccf8.png

Add stack leader Wechat registration study

d5c66b698ed3918e37f7eebf94c197a5.jpeg

Please note for fast pass: 399

Guess you like

Origin blog.csdn.net/youanyyou/article/details/132309770