Использование InheritableThreadLocal с ThreadPoolExecutor - или - ThreadPoolExecutor, который не повторно использует потоки
Я пытаюсь использовать как InheritableThreadLocal
, так и ThreadPoolExecutor
.
Это прерывается, потому что ThreadPoolExecutor
повторно использует потоки для каждого пула (в конце концов, это пул), что означает, что InheritableThreadLocal
работает не так, как ожидалось. Теперь проблема кажется мне очевидной, но она была особенно ловушкой для отслеживания.
Я использую InheritableThreadLocal
, чтобы каждый из нескольких процессов верхнего уровня имел собственное соединение с базой данных для себя и любых подпроцессов, которые он порождает. Я не просто использую один общий пул соединений, потому что каждый процесс верхнего уровня будет выполнять много многошаговую работу с его соединением перед тем, как совершить работу с базой данных и/или подготовить много PreparedStatements, которые используются снова и снова.
Я использую общий ThreadPoolExecutor
между этими процессами верхнего уровня, потому что есть определенные поведения, которые необходимо закрыть. например Несмотря на то, что у меня может быть 4 процесса верхнего уровня, у меня может быть только один процесс записи в базу данных за раз (или система должна затвора на каком-то другом общем ресурсе). Таким образом, процесс верхнего уровня создаст Runnable
и отправлю его в общий ThreadPoolExecutor
, чтобы убедиться, что одновременно выполняется не более одного (или два или три в зависимости от случая) всей системы.
Проблема состоит в том, что, поскольку ThreadPoolExecutor
повторно использует свои потоки для пулов, InheritableThreadLocal
собирает исходное значение, которое было запущено в этом пуле, а не значение, которое было в процессе верхнего уровня, который отправил Выполняется до ThreadPoolExecutor
.
-
Есть ли способ заставить рабочий пул в ThreadPoolExecutor
использовать значение InheritableThreadLocal
, которое было в контексте процесса, который создал Runnable, а не в контексте повторно используемого пула потоков?
-
Альтернативно, существует ли какая-либо реализация ThreadPoolExecutor
, которая создает новый поток каждый раз при запуске нового Runnable? Для моих целей мне только заботится о том, чтобы количество одновременных потоков на фиксированный размер.
-
Есть ли у кого-нибудь другое решение или предложение, чтобы я выполнил то, что я описал выше?
(Хотя я понимаю, что могу решить эту проблему, передав соединение с базой данных из класса в класс в subthread на subthread, например, на какой-то общинный велосипед, я хотел бы избежать этого.)
Есть предыдущий вопрос о StackOverflow, InheritableThreadLocal и пулах потоков, который также затрагивает эту проблему. Однако решение этой проблемы заключается в том, что это плохой вариант использования InheritableThreadLocal, который, как мне кажется, не относится к моей ситуации.
Спасибо за любые идеи.
Ответы
Ответ 1
Вместо использования ThreadPoolExecutor для защиты общих ресурсов, почему бы не использовать java.util.concurrent.Semaphore
? Созданные подзадачи будут выполняться до завершения в своих потоках, но только после получения разрешения семафора и, конечно же, отпускания разрешения, когда это будет сделано.
Ответ 2
использование InheritedThreadLocal
почти наверняка неверно. Вероятно, вы бы не задали вопрос, можете ли вы поместиться в этот странный инструмент.
Прежде всего это ужасно подвержено утечке, и часто значение ускользает в некоторых совершенно странных нитях.
Что касается Runnable, то ассоциируется w/контекст.
Переопределите public void execute(Runnable command)
в ExecutorPool
и оберните Runnable
с помощью некоторого контекста, несущего значение, которое вы хотите в первую очередь, из InheritedThreadLocal
.
Класс упаковки должен выглядеть примерно так:
class WrappedRunnable extends Runnable{
static final ThreadLocal<Ctx> context=new ThreadLocal<Ctx>();
final Runnable target;
final Ctx context;
WrappedRunnable(Ctx context, Runnable target){...}
public void run(){
ctx.set(context);
try{
target.run();
}finally{
ctx.set(null);//or ctx.remove()
}
}
}
В качестве альтернативы существует ли какая-либо реализация ThreadPoolExecutor, которая создает новый поток при каждом запуске нового Runnable? Для моих целей мне только заботится о том, чтобы > число одновременных потоков на фиксированный размер.
Поистине плохой с точки зрения производительности, вы можете реализовать свой собственный, в основном вам нужен только execute(Runnable task)
метод для Executor
, который порождает новый поток и запускает его.
Ответ 3
У нас была такая же проблема ранее, и мы решили эту проблему, написав ThreadLocalContextMigrator, которая в основном копирует локальный контекст потока в задачу, которая будет выполнена с использованием потока из пула. Задача во время выполнения будет собирать больше информации о контенте и после завершения задачи мы ее скопируем.
Ответ 4
Почему бы просто не передать текущее соединение на какие-либо подзадачи, порожденные главной задачей? может быть, какой-то общий объект Context?