Пул потоков — важная концепция многопоточного программирования на Java. Он позволяет эффективно управлять ресурсами потоков и повторно использовать их, а также повышать производительность и стабильность системы. Однако при использовании пулов потоков существуют некоторые меры предосторожности и распространенные ошибки. Если вы не будете осторожны, это может вызвать серьезные проблемы, такие как утечки памяти, взаимоблокировки, снижение производительности и т. д.
В этой статье будут представлены пять ошибок неправильного использования пулов потоков, а также способы их предотвращения и решения.
Часто возникает вопрос о добавлении обработки исключений, когда пул потоков выполняет методы. Однако до недавнего времени некоторые из моих коллег все еще совершали эту ошибку, поэтому я все еще хочу поговорить об этом, но я также упомянул элегантное глобальное исключение пула потоков. О методе обработки вы можете прочитать ниже.
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
Future<Integer> submit = threadPoolExecutor.execute(() -> {
int i = 1 / 0; // Произошло исключение
return i;
});
}
Как показано в приведенном выше коде, обработка исключений не добавляется, когда пул потоков выполняет задачи. Когда в задаче возникает исключение, внутренние ошибки не могут быть записаны.
Добавьте обработку try/catch в метод задачи выполнения пула потоков. Код выглядит следующим образом:
@Test
public void test() throws Exception {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
Future<Integer> submit = threadPoolExecutor.execute(() -> {
try {
int i = 1 / 0;
return i;
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
});
}
Когда пул потоков вызывает множество методов задачи, обработка try/catch должна быть добавлена к каждому методу выполнения задачи пула потоков, что не является элегантным. На самом деле класс пула потоков ThreadPoolExecutor поддерживает передачу параметра ThreadFactory для настройки фабрики потоков. Таким образом, когда мы создаем поток, мы можем указать метод обработки исключений setUncaughtExceptionHandler.
Таким образом, исключения могут обрабатываться глобально. Код выглядит следующим образом:
ThreadFactory threadFactory = r -> {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler((t, e) -> {
// Регистрация исключений потоков
log.error(e.getMessage(), e);
});
return thread;
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
threadPoolExecutor.execute(() -> {
log.info("---------------------");
int i = 1 / 0;
});
Однако следует отметить, что приведенный выше метод setUncaughtExceptionHandler может обрабатывать исключения только глобально для метода выполнения пула потоков. Невозможно обработать метод отправки пула потоков.
В Java политику отклонения пула потоков можно назвать обычной проблемой эссе, состоящей из восьми частей. Хотя все помнят, что пул потоков имеет четыре решающие стратегии, при реальном написании кода я обнаружил, что большинство людей используют только стратегию CallerRunsPolicy (задачу выполняет вызывающий поток). Я уже терпел эту потерю раньше, поэтому я расскажу об этом.
Когда-то существовал онлайн-бизнес-интерфейс, который использовал пул потоков для вызовов стороннего интерфейса. Политика отклонения в конфигурации пула потоков использовала CallerRunsPolicy. Пример кода выглядит следующим образом:
// Кто-то онлайн конфигурация потока следующая
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // Минимальное количество основных потоков
50, // Максимальное количество потоков, когда очередь заполнена, максимальное количество потоков, которые можно создать.
60L, TimeUnit.SECONDS, // Когда количество простаивающих потоков превышает количество основных потоков, максимальное время ожидания повторного использования потока
new LinkedBlockingQueue<>(5000), // Размер очереди блокировки: когда основной поток заполнен, в очередь будут помещены новые потоки.
new CustomizableThreadFactory("task"), // Пользовательское имя темы
new ThreadPoolExecutor.CallerRunsPolicy() // Политика запрета на выполнение потоков
);
threadPoolExecutor.execute(() -> {
// Вызов стороннего интерфейса
...
});
Если сторонний интерфейс работает ненормально и задача пула потоков вызывает сторонний интерфейс и время ожидания истекает, в результате чего количество основных потоков, максимальное количество потоков накапливается, а очередь блокировки заполняется, политика отклонения будет Однако, поскольку используется стратегия CallerRunsPolicy, задача потока выполняется непосредственно нашим бизнес-потоком.
Поскольку сторонний интерфейс является ненормальным, время выполнения бизнес-потока будет продолжать истекать. Контейнер Tomcat, используемый для онлайн-сервисов, в конечном итоге приведет к заполнению максимального количества потоков Tomcat, и он не сможет продолжать работу. предоставлять услуги внешнему миру.
Прежде всего, мы должны учитывать доступность бизнес-интерфейса. Даже если задача пула потоков отброшена, это не должно повлиять на бизнес-интерфейс.
Когда стабильность бизнес-интерфейса гарантирована и учитывая важность задачи пула потоков, если она не очень важна, вы можете использовать политику DiscardPolicy, чтобы напрямую ее отменить. Если это очень важно, вы можете рассмотреть возможность использования сообщения. очередь на замену пула потоков.
Я не знаю, сталкивались ли вы когда-нибудь с этой проблемой, но я определенно ее решил. В конечном итоге я не думал о бизнес-логике перед написанием кода, поэтому я просто начал писать и сделал один шаг вперед. время 😂. Поэтому очень важна логическая сортировка, разделение и проектирование кода перед его написанием.
Причина этой проблемы очень проста: пул потоков повторно создается внутри метода и не закрывается после выполнения. Более классическая проблема заключается в том, что эта проблема может возникнуть при использовании пула потоков в запланированной задаче. Пример кода выглядит следующим образом:
@XxlJob("test")
public void test() throws Exception {
// Кто-то онлайн конфигурация потока следующая
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // Минимальное количество основных потоков
50, // Максимальное количество потоков, когда очередь заполнена, максимальное количество потоков, которые можно создать.
60L, TimeUnit.SECONDS, // Когда количество простаивающих потоков превышает количество основных потоков, максимальное время ожидания повторного использования потока
new LinkedBlockingQueue<>(5000), // Размер очереди блокировки: когда основной поток заполнен, в очередь будут помещены новые потоки.
new CustomizableThreadFactory("task"), // Пользовательское имя темы
new ThreadPoolExecutor.CallerRunsPolicy() // Политика запрета на выполнение потоков
);
threadPoolExecutor.execute(() -> {
// Логика задачи
...
});
}
Когда мы хотим использовать пул потоков в запланированной задаче, чтобы сократить время выполнения задачи, мы должны быть осторожны, чтобы не создать пул потоков внутри задачи. Как только мы это сделаем, мы, по сути, обнаружим, что программа внезапно зависает после запуска. период времени осталось куча файлов ошибок дампа памяти 😂.
Используйте одиночные пулы потоков и никогда не создавайте пулы потоков повторно. Пример кода выглядит следующим образом:
// Кто-то онлайн конфигурация потока следующая
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
50, // Минимальное количество основных потоков
50, // Максимальное количество потоков, когда очередь заполнена, максимальное количество потоков, которые можно создать.
60L, TimeUnit.SECONDS, // Когда количество простаивающих потоков превышает количество основных потоков, максимальное время ожидания повторного использования потока
new LinkedBlockingQueue<>(5000), // Размер очереди блокировки: когда основной поток заполнен, в очередь будут помещены новые потоки.
new CustomizableThreadFactory("task"), // Пользовательское имя темы
new ThreadPoolExecutor.CallerRunsPolicy() // Политика запрета на выполнение потоков
);
@XxlJob("test")
public void test() throws Exception {
threadPoolExecutor.execute(() -> {
// Логика задачи
// ...
});
}
Иногда нам может потребоваться сэкономить ресурсы потоков и поместить различные типы задач в один и тот же пул потоков для выполнения, например первичную бизнес-логику и вторичное ведение журнала, мониторинг и т. д. Это кажется разумным, но на самом деле это может привести к тому, что одна задача повлияет на другую задачу или даже вызовет проблемы взаимоблокировки.
Причина проблемы в том, что разные типы задач могут иметь разное время выполнения, приоритеты, зависимости и т. д. Если они помещены в один пул потоков, могут возникнуть следующие ситуации:
Решение тоже очень простое,Просто используйте разные потоки пулов для выполнения разных типов задач.,Распределяйте ресурсы потоков в соответствии с характеристиками и важностью задач.,Избегайте влияния одной задачи на другую. Конкретно,Есть несколько предложений:
ThreadLocal — это класс инструментов, предоставляемый Java, который позволяет каждому потоку иметь собственную копию переменных для достижения изоляции данных между потоками, например, для хранения некоторой контекстной информации, связанной с потоком, такой как идентификатор пользователя, идентификатор запроса и т. д. Это кажется полезным, но при использовании вместе с пулом потоков могут возникнуть некоторые неожиданные проблемы, такие как повреждение данных, утечки памяти и т. д.
Причина проблемы в том, что концепции проектирования ThreadLocal и пула потоков противоречат друг другу. ThreadLocal основан на потоках, а пул потоков основан на задачах. В частности, есть следующие вопросы:
Решение тоже очень простое,취дав использовании ThreadLocal При работе с пулами потоков обратите внимание на следующие моменты:
Эта статья знакомит вас с пятью ловушками неправильного использования пулов потоков, включая ненормальное исчезновение в пуле потоков, неправильные настройки стратегии принятия решения о пуле потоков, повторное создание пулов потоков, приводящее к переполнению памяти, использование одного и того же пула потоков для выполнения разных типов. задач и использовать ThreadLocal Проблемы несовместимости с пулами потоки и их причина проблемы и решение. Надеюсь, что этот контент будет полезен всем.
Если вы считаете, что эта статья хорошо написана, вы можете поставить лайк и подписаться на нее. Я буду обновлять новые статьи технической информацией, обучением проектам и обменом практическим опытом.