Некоторые настройки, которые вы должны знать о пулах потоков

Java

Общедоступная учетная запись WeChat «Back-end Advanced», ориентированная на совместное использование серверных технологий: Java, Golang, WEB-инфраструктура, распределенное промежуточное программное обеспечение, управление услугами и т. д.
Старый водитель научил тебя всем деньгам и довел до продвинутого уровня, я не успел объяснить и сесть в автобус!

После прочтения моей последней статьи"Вы понимаете параметры создания пула потоков?«После этого, когда вы сталкиваетесь с такого рода вопросом, вы чувствуете, что можете полностью одурачить интервьюера, а 50 тысяч легко получить. Как всем известно, если в этот момент интервьюер дает вам встречное убийство:

Можно ли предварительно создавать потоки при инициализации пула потоков? Можно ли повторно использовать основные потоки пула потоков? Зачем?

Если вы запутались в этот момент, вам следует паниковать, проблема очень большая, 50к сразу станут 5к.

Осторожные пользователи сети давно задумались над этим вопросом:

В пуле потоков ThreadPoolExecutor есть несколько менее часто используемых настроек.Я предлагаю вам не использовать эти настройки, если у вас нет особых требований в сценарии вашего приложения.

Можно ли предварительно создавать потоки при инициализации пула потоков?

prestartAllCoreThreads

При инициализации пула потоков вы можете создать потоки заранее.После инициализации пула потоков вызовите метод prestartAllCoreThreads() для предварительного создания основных потоков с номером corePoolSize.Давайте посмотрим на исходный код:

public int prestartAllCoreThreads() {
    int n = 0;
    while (addWorker(null, true))
        ++n;
    return n;
}
private boolean addWorker(Runnable firstTask, boolean core) {
  // ..
}

Цель метода addWorker — добавить задачи в пул потоков и выполнить их,Если задача пуста, то когда поток получает задачу для выполнения, вызывается метод getTask(), который блокирует выполнение полученной задачи из очереди блокировки blockingQueue, поэтому поток не освобождается и остается в потоке. бассейн., если core=true, это означает, что задача может быть выполнена только основным потоком.

Таким образом, этот метод всегда будет предварительно создавать потоки без выполнения задачи в пуле потоков, а число — corePoolSize.

Давайте проверим это:

По результатам теста в пуле потоков есть предварительно созданные неактивные потоки corePoolSize.

prestartCoreThread

prestartCoreThread() также может создавать потоки заранее, но этот метод создаст только один поток.Давайте посмотрим на исходный код:

public boolean prestartCoreThread() {
    return workerCountOf(ctl.get()) < corePoolSize &&
        addWorker(null, true);
}

Из исходного кода метода, если количество рабочих потоков меньше, чем corePoolSize в это время, вызывается addWorker для создания бездействующего основного потока.

Давайте проверим это:

По результатам теста в пуле потоков был предварительно создан один бездействующий поток.

Можно ли повторно использовать основные потоки пула потоков?

Вы можете подумать, что число corePoolSize установлено равным 0, так что все потоки в пуле потоков являются «временными», и выживает только keepAliveTime.Ваша идея может быть правильной, но задумывались ли вы об очень серьезных последствиях, когда corePoolSize= 0 задача должна заполнить очередь блокировки перед созданием потока для выполнения задачи.Хорошо, что очередь блокировки имеет заданную длину.Если длина очереди бесконечна, вы можете просто дождаться исключения OOM, поэтому этот параметр поведение — это не все, что нам нужно.

Есть ли какие-либо настройки для повторного использования основных потоков?

allowCoreThreadTimeOut

ThreadPoolExecutor имеет закрытую переменную-член:

private volatile boolean allowCoreThreadTimeOut;

Если allowCoreThreadTimeOut=true, основной поток будет перезапущен в течение указанного времени.

Я также сказал выше, что когда поток простаивает, он будет блокировать выполнение задач из очереди блокировки blockingQueue, поэтому давайте посмотрим, чтобы убедиться, что основной поток не уничтожен, мы непосредственно находим часть исходного кода:

java.util.concurrent.ThreadPoolExecutor#getTask:

boolean timedOut = false; // Did the last poll() time out?
for (;;) {
    // Are workers subject to culling?
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

Значение ключа здесь timed, если allowCoreThreadTimeOut=true или рабочий поток больше, чем corePoolSize, timed=true, если timed=true, то будет вызван метод poll() для получения задачи из блокирующей очереди, иначе take( ) будет вызываться метод для получения задачи.

Позвольте мне объяснить эти два метода ниже:

  1. poll(long timeout, TimeUnit unit): Вынуть задачу из BlockingQueue. Если ее нельзя вывести сразу, можно дождаться времени, заданного параметром тайм-аута. Если задачу нельзя вынуть по истечении этого времени, она вернет null ;
  2. take(): взять задачу из очереди блокировки.Если BlockingQueue пуста, блок переходит в состояние ожидания до тех пор, пока в BlockingQueue не будет добавлена ​​новая задача.

Здесь у нас есть хорошее объяснение,Когда allowCoreThreadTimeOut=true или рабочий поток больше, чем corePoolSize, поток вызывает метод опроса BlockingQueue для получения задачи. Метод в потоке выходит из цикла while, поток будет перезапущен следующим.

Давайте проверим это:

Как видите, основная нить переработана.

公众号「后端进阶」,专注后端技术分享!