Почему дочерний поток, использующий TreadPoolExecutor, не должен быть представлен какому-либо унаследованному контексту родительского потока?
Я сделал компонент, реализующий ApplicationListener<AuthenticationSuccessEvent>
и должен регистрировать IP-адрес, IP-адрес можно получить из HttpServletRequest
. По-видимому, этот компонент запускается в дочернем потоке, поэтому мне нужно было установить свойствоThreadContextInheritable
верно на DispatcherServlet
компонент, чтобы получить доступ к HttpServletRequest
. (Я пробовал использовать RequestContextListener, но это не помогло).
В документации Spring, которую можно найти здесь, указано следующее предупреждение, когда вы устанавливаетеThreadContextInheritable
к true
.
ПРЕДУПРЕЖДЕНИЕ. Не используйте наследование для дочерних потоков, если вы обращаетесь к пулу потоков, который настроен на добавление новых потоков по запросу (например, JDK ThreadPoolExecutor), поскольку это приведет к раскрытию унаследованного контекста для такого объединенного потока.
У меня вопрос: почему раскрытие унаследованного контекста объединенному потоку может быть таким плохим? Что могло пойти не так в таком случае? Кроме того, означают ли они дочерний поток, который используетThreadPoolExecutor
экземпляр ИЛИ означают ли они дочерний поток, созданный с использованием ThreadPoolExecutor
?
0 ответов
InheritableThreadLocal расширяет ThreadLocal и используется, когда нам нужно автоматически передать значения локальных атрибутов родительского потока дочернему потоку при создании.
Это наследование отлично работает, когда вы каждый раз создаете новый дочерний поток и не используете повторно уже созданный поток.
Давайте рассмотрим сценарий - у нас есть пул потоков размером 5. Когда мы отправляем первые 5 задач в пул потоков для обработки для каждой задачи, создается новый поток, поэтому наследование локального атрибута потока работает отлично. Проблема начинается с 6-го запроса. Когда вы отправляете 6-ю задачу, новый поток не создается, но уже созданный поток из пула потоков повторно используется для его обработки. В 6-м запросе значения локальных атрибутов родительского потока не наследуются дочернему потоку, поскольку мы не создаем новый поток, а повторно используем уже созданный поток из пула.
Этот фрагмент кода объяснит суть -
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DeadlyComboTest {
public static void main(String[] args) throws InterruptedException {
int noOfThreads = 5;
//Execution 1
int threadPoolSize = noOfThreads;
//Execution 2 : uncomment below line and comment line above
//int threadPoolSize = noOfThreads/2;
//1. create thread pool
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
//2. create Inheritable thread local
InheritableThreadLocal<Object> value = new InheritableThreadLocal<>();
//3. create new command and execute using thread pool created in step 1
for (int i = 1; i <= noOfThreads; i++) {
value.set(i);
executor.execute(() -> printThreadLocalValue(value));
}
executor.shutdown();
}
private static void printThreadLocalValue(ThreadLocal<Object> value) {
System.out.println(Thread.currentThread().getName() + " = " + value.get());
}
}
Выполнение 1: noOfThreads = threadPoolSize = 5 OUTPUT: you may get the output in different sequence
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-3 = 3
pool-1-thread-4 = 4
pool-1-thread-5 = 5
Все выглядит хорошо. Поскольку при каждом запросе создается новый поток, а дочерний поток правильно наследует локальные значения родительского потока.
Выполнение 2: noOfThreads = 5 && threadPoolSize = noOfThreads/2 = 2 OUTPUT:
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
Только для первых двух запросов наследование локального потока работает правильно, поскольку размер пула потоков равен 2, поэтому для первых двух запросов создаются и объединяются новые потоки. При третьем запросе повторно используются уже созданные потоки из пула, которые все еще имеют унаследованные значения старого потока.
Выполнение 2 - это реальный сценарий, в котором потоки из пула будут повторно использоваться.
Что может пойти не так, если мы используем комбинацию пула потоков и InheritableThreadLocal?
Это вызовет непреднамеренную утечку информации, поскольку рабочий / дочерний поток из пула будет передавать значения локальных атрибутов потока одного потока другому.
Если ваша бизнес-логика зависит от значений этих атрибутов, вам будет сложно отслеживать ошибки.
Вот почему документация Spring предостерегает от использования этой комбинации пула потоков и InheritableThreadLocal.