#java #multithreading #java-8 #concurrency
#java #многопоточность #java-8 #параллелизм
Вопрос:
Сегодня я обнаружил, что в моих приложениях Java 8 многие потоки находятся в состоянии ОЖИДАНИЯ:
[arthas@1]$ thread --state RUNNABLE
Threads Total: 3427, NEW: 0, RUNNABLE: 17, BLOCKED: 0, WAITING: 3114, TIMED_WAITING: 296, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
124 pool-11-thread-25 main 5 RUNNABLE 75 0:0 false false
53 as-command-execute-daemon system 10 RUNNABLE 23 0:0 false true
133 Thread-20 main 5 RUNNABLE 1 0:2 false true
28 Apollo-RemoteConfigLongPollService-1 Apollo 5 RUNNABLE 0 0:0 false true
32 Attach Listener system 9 RUNNABLE 0 0:0 false true
99 DestroyJavaVM main 5 RUNNABLE 0 0:39 false false
4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true
19 grpc-default-worker-ELG-1-1 main 5 RUNNABLE 0 0:0 false true
21 grpc-default-worker-ELG-1-2 main 5 RUNNABLE 0 0:0 false true
97 http-nio-11003-Acceptor main 5 RUNNABLE 0 0:0 false true
85 http-nio-11003-BlockPoller main 5 RUNNABLE 0 0:0 false true
96 http-nio-11003-ClientPoller main 5 RUNNABLE 0 0:0 false true
54 lettuce-nioEventLoop-4-1 main 5 RUNNABLE 0 0:0 false true
70 lettuce-nioEventLoop-4-2 main 5 RUNNABLE 0 0:0 false true
36 nioEventLoopGroup-3-1 system 10 RUNNABLE 0 0:0 false false
42 nioEventLoopGroup-3-2 system 10 RUNNABLE 0 0:0 false false
37 nioEventLoopGroup-4-1 system 10 RUNNABLE 0 0:0 false false
Affect(row-cnt:0) cost in 120 ms.
поток более 3000 находится в состоянии ОЖИДАНИЯ, теперь я выбираю случайный поток пула ОЖИДАЮЩИХ потоков, который отображается следующим образом::
[arthas@1]$ thread 4410
"pool-96-thread-10" Id=4410 WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@3e27c029
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@3e27c029
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Affect(row-cnt:0) cost in 16 ms.
но проблема в том, что я не знаю, с чего начался поток и что заставляет ожидающий поток увеличиваться. Есть ли какой-либо способ узнать, с чего начать поток или почему ОЖИДАЮЩИЙ поток увеличивается? Сейчас я использую Java ThreadExecutor. Прямо сейчас ожидающий поток достигает 6000 . Я добавляю пользовательскую конфигурацию:
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(30));
}
}
Комментарии:
1. Используете ли вы
ScheduledExecutorService
где-нибудь в своем приложении, настроенном на 3000 потоков, которому не часто приходится запускать задачи?2. Сейчас я использую расписание cron в своем проекте. @BeUndead
3. Настроен ли он для возможности повторного использования потоков надлежащим образом? Вы дали ему настройку
Queue
при создании, которая ничего не может содержать и поэтому каждый раз создает новый поток? Похоже, что он постоянно растет каждую минуту. Запланировано ли что-нибудь CRON на каждую минуту, что может приводить к ошибкам и оставлять потоки в непригодном для использования состоянии?4. Я предполагаю , что ваш код продолжает создавать новые экземпляры
ScheduledThreadPoolExecutor
и никогда не закрывает их. Код должен создать только один экземпляр, а затем повторно использовать его. Это цель пула потоков, повторное использование потоков в пуле. Короче говоря, у вас утечка ресурсов, поэтому просмотрите код и найдите, гдеScheduledThreadPoolExecutor
инициализируется объект.5. Я не нашел ни
ScheduledThreadPoolExecutor
одного класса в своем проекте, но я настраиваю задачу расписания как многопоточную. @Andreas
Ответ №1:
Показанная вами трассировка стека является «нормальной ситуацией»: это поток-исполнитель threadpool, который готов к выполнению работы, но очередь работы пуста. В этом случае «ожидание» означает: я жду выполнения задания, а не: «Мне нужно что-то сделать, но я не могу этого сделать, потому что я жду, когда материал будет завершен первым».
Теперь 3000 потоков сами по себе вызывают некоторую озабоченность; у каждого потока есть свое собственное пространство стека. Насколько это велико, зависит от вашего -Xss
параметра, но они, как правило, увеличиваются с 64 КБ до 1 МБ. Если это 1 МБ, это… Это 3 ГБ стекового пространства… неоптимально. Это число (количество потоков, ожидающих принятия задания) также не должно сильно увеличиваться после того, как виртуальная машина разогрелась.
Если все / большинство из этих ОЖИДАЮЩИХ потоков имеют аналогичную трассировку, то на самом деле есть только два варианта:
- Вы создали исполнителя и продолжаете просить его со временем добавлять все больше и больше потоков. Я сомневаюсь в этом, но это возможно.
- Вы продолжаете создавать исполнителей. Не делайте этого.
Идея исполнителя заключается в том, что вы создаете только один или, по крайней мере, очень и очень немногие из них.
Если вы ДОЛЖНЫ создавать их как часть вашего запущенного приложения (в отличие от обычной процедуры создания заданий и передачи их исполнителю singleton), то имейте в виду, что они фактически являются ресурсами: если вы не «закроете» их, ваш процесс будет требовать все больше и больше ресурсов, пока в конечном итоге виртуальная машина не будет запущена.сбой при его завершении.
Чтобы закрыть их, вы вызываете shutdown()
, который запрашивает красиво, а shutdownNow()
который более агрессивен, и все еще не выполненные задания будут отменены навсегда.
Итак, чтобы резюмировать:
- Вы создаете новых исполнителей во время обычной обработки в своем приложении. Найдите
new ScheduledThreadPoolExecutor
в своей кодовой базе и проверьте ситуацию. Добавьте некоторое ведение журнала, если вам нужно увидеть это в действии. - Тогда, скорее всего, вы хотите исправить это и в первую очередь прекратить создавать новых исполнителей — просто создайте один раз и передайте задания этому исполнителю.
- Если действительно имеет смысл их создавать, используйте некоторую конструкцию guardian, чтобы убедиться, что вы также очистите их, когда закончите их использовать. Вы можете поискать, как сделать это безопасно; это немного сложно, так как вам нужно решить, что делать с любыми заданиями в очереди, которые еще не выполнены. Если это не проблема, все просто:
.shutdown()
выполнит работу.
Комментарии:
1. Может ли создание новых исполнителей для каждого нового входящего запроса вызывать проблему даже после вызова shutdown() после обработки запроса?
2. Определите «проблему». Я сомневаюсь в этом.
3. Мы наблюдаем высокое использование встроенной памяти в нашем сервисе. Размер кучи не увеличивается. При проверке потоков в этом контейнере я обнаружил, что около 10 тыс. потоков были созданы как часть новых объектов службы исполнителя. shutodwn() вызывается для этих объектов службы исполнителя, но эти потоки все еще задерживались. Могут ли они привести к увеличению потребления встроенной памяти?
4. Объект завершения
Thread
работы обычно похож на любой другой объект; Он будет GCed при необходимости. Как правило, процесс Java всегда использует большую память: он использует все это, а затем управляет своей собственной кучей. Я не думаю, что комментарии SO приведут к просветлению здесь, я боюсь — управление памятью Java на несколько порядков слишком сложно.5. Но может ли так много потоков в состоянии ожидания из-за создания новых объектов службы исполнителя привести к увеличению потребления встроенной памяти отдельно от кучи? Это то, что я наблюдаю и пытаюсь понять / проверить