[Справочник по подводным камням] Пять ловушек неправильного использования пулов потоков
[Справочник по подводным камням] Пять ловушек неправильного использования пулов потоков

Пул потоков — важная концепция многопоточного программирования на Java. Он позволяет эффективно управлять ресурсами потоков и повторно использовать их, а также повышать производительность и стабильность системы. Однако при использовании пулов потоков существуют некоторые меры предосторожности и распространенные ошибки. Если вы не будете осторожны, это может вызвать серьезные проблемы, такие как утечки памяти, взаимоблокировки, снижение производительности и т. д.

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

Яма 1: Аномальное исчезновение в пуле потоков

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

Причина проблемы

Язык кода:javascript
копировать
@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 в метод задачи выполнения пула потоков. Код выглядит следующим образом:

Язык кода:javascript
копировать
@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.

Таким образом, исключения могут обрабатываться глобально. Код выглядит следующим образом:

Язык кода:javascript
копировать
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 может обрабатывать исключения только глобально для метода выполнения пула потоков. Невозможно обработать метод отправки пула потоков.

Ошибка 2: неправильный параметр политики отклонения приводит к тайм-ауту интерфейса.

В Java политику отклонения пула потоков можно назвать обычной проблемой эссе, состоящей из восьми частей. Хотя все помнят, что пул потоков имеет четыре решающие стратегии, при реальном написании кода я обнаружил, что большинство людей используют только стратегию CallerRunsPolicy (задачу выполняет вызывающий поток). Я уже терпел эту потерю раньше, поэтому я расскажу об этом.

Причина проблемы

Когда-то существовал онлайн-бизнес-интерфейс, который использовал пул потоков для вызовов стороннего интерфейса. Политика отклонения в конфигурации пула потоков использовала CallerRunsPolicy. Пример кода выглядит следующим образом:

Язык кода:javascript
копировать
// Кто-то онлайн конфигурация потока следующая
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        50, // Минимальное количество основных потоков
        50, // Максимальное количество потоков, когда очередь заполнена, максимальное количество потоков, которые можно создать.
        60L, TimeUnit.SECONDS, // Когда количество простаивающих потоков превышает количество основных потоков, максимальное время ожидания повторного использования потока
        new LinkedBlockingQueue<>(5000), // Размер очереди блокировки: когда основной поток заполнен, в очередь будут помещены новые потоки.
        new CustomizableThreadFactory("task"), // Пользовательское имя темы
        new ThreadPoolExecutor.CallerRunsPolicy() // Политика запрета на выполнение потоков
);

threadPoolExecutor.execute(() -> {
    // Вызов стороннего интерфейса
    ...
});

Если сторонний интерфейс работает ненормально и задача пула потоков вызывает сторонний интерфейс и время ожидания истекает, в результате чего количество основных потоков, максимальное количество потоков накапливается, а очередь блокировки заполняется, политика отклонения будет Однако, поскольку используется стратегия CallerRunsPolicy, задача потока выполняется непосредственно нашим бизнес-потоком.

Поскольку сторонний интерфейс является ненормальным, время выполнения бизнес-потока будет продолжать истекать. Контейнер Tomcat, используемый для онлайн-сервисов, в конечном итоге приведет к заполнению максимального количества потоков Tomcat, и он не сможет продолжать работу. предоставлять услуги внешнему миру.

Решение

Прежде всего, мы должны учитывать доступность бизнес-интерфейса. Даже если задача пула потоков отброшена, это не должно повлиять на бизнес-интерфейс.

Когда стабильность бизнес-интерфейса гарантирована и учитывая важность задачи пула потоков, если она не очень важна, вы можете использовать политику DiscardPolicy, чтобы напрямую ее отменить. Если это очень важно, вы можете рассмотреть возможность использования сообщения. очередь на замену пула потоков.

Ямка 3. Постоянное создание пулов потоков приводит к переполнению памяти.

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

Причина проблемы

Причина этой проблемы очень проста: пул потоков повторно создается внутри метода и не закрывается после выполнения. Более классическая проблема заключается в том, что эта проблема может возникнуть при использовании пула потоков в запланированной задаче. Пример кода выглядит следующим образом:

Язык кода:javascript
копировать
@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(() -> {
        // Логика задачи
        ...
    });
}

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

Решение

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

Язык кода:javascript
копировать
// Кто-то онлайн конфигурация потока следующая
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(() -> {
        // Логика задачи
        // ...
    });
}

Ямка 4. Совместное использование пулов потоков для выполнения разных типов задач приводит к низкой эффективности.

Иногда нам может потребоваться сэкономить ресурсы потоков и поместить различные типы задач в один и тот же пул потоков для выполнения, например первичную бизнес-логику и вторичное ведение журнала, мониторинг и т. д. Это кажется разумным, но на самом деле это может привести к тому, что одна задача повлияет на другую задачу или даже вызовет проблемы взаимоблокировки.

Причина проблемы

Причина проблемы в том, что разные типы задач могут иметь разное время выполнения, приоритеты, зависимости и т. д. Если они помещены в один пул потоков, могут возникнуть следующие ситуации:

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

Решение

Решение тоже очень простое,Просто используйте разные потоки пулов для выполнения разных типов задач.,Распределяйте ресурсы потоков в соответствии с характеристиками и важностью задач.,Избегайте влияния одной задачи на другую. Конкретно,Есть несколько предложений:

  • Для основной бизнес-логики используйте выделенный пул. потоки, в зависимости от параллельности и времени отклика бизнеса, устанавливают соответствующий пул параметры потока для обеспечения нормальной работы и эффективной обработки бизнеса.
  • Для вторичной регистрации, мониторинга и т. д.,Используйте отдельные потоки пула, установите соответствующий пул в зависимости от частоты и важности задачи Параметр потоков обеспечивает асинхронное выполнение задач и не влияет на основной бизнес.
  • Для задач с зависимостями,Используйте отдельные потоки пула, в зависимости от количества и сложности задач, задайте соответствующий пул Параметр потоков обеспечивает упорядоченное выполнение задач и не вызывает взаимоблокировок.

Ячейка 5. Проблемы несовместимости между использованием ThreadLocal и пулами потоков.

ThreadLocal — это класс инструментов, предоставляемый Java, который позволяет каждому потоку иметь собственную копию переменных для достижения изоляции данных между потоками, например, для хранения некоторой контекстной информации, связанной с потоком, такой как идентификатор пользователя, идентификатор запроса и т. д. Это кажется полезным, но при использовании вместе с пулом потоков могут возникнуть некоторые неожиданные проблемы, такие как повреждение данных, утечки памяти и т. д.

Причина проблемы

Причина проблемы в том, что концепции проектирования ThreadLocal и пула потоков противоречат друг другу. ThreadLocal основан на потоках, а пул потоков основан на задачах. В частности, есть следующие вопросы:

  • ThreadLocal Переменная да привязана к существованию в потоке, а пул поток потока можно использовать повторно. Если поток не очищается после выполнения задачи, ThreadLocal переменная, то эта переменная будет унаследована следующей выполняемой задачей, что приведет к путанице данных.
  • Переменные ThreadLocal хранятся в атрибуте типа ThreadLocalMap класса Thread. Этот атрибут представляет собой слабую ссылку Map, ключом которой является объект ThreadLocal, а значением является копия переменной. Если объект ThreadLocal будет переработан, его ключи станут недействительными, но значения останутся в Map, что приведет к утечкам памяти.

Решение

Решение тоже очень простое,취дав использовании ThreadLocal При работе с пулами потоков обратите внимание на следующие моменты:

  • Прежде чем использовать переменные ThreadLocal, убедитесь, что для каждого потока установлено правильное начальное значение, чтобы избежать использования устаревших значений из предыдущей задачи.
  • После использования переменных ThreadLocal своевременно очищайте переменные ThreadLocal, чтобы копия переменной не была унаследована следующей выполняемой задачей или не занимала пространство памяти, вызывая утечки памяти. Вы можете использовать оператор try-finally или использовать интерфейс AutoCloseable, предоставляемый Java 8, для реализации функции автоматической очистки.
  • в использовании ThreadLocal , пожалуйста, обратите внимание на пул размер потока и количество задач, чтобы избежать создания слишком большого количества ThreadLocal Копии объектов и переменных приводят к чрезмерному использованию памяти. Вы можете использовать некоторые инструменты, такие как VisualVM, для мониторинга пула потокови ThreadLocal статус, своевременно обнаруживать и решать проблемы.

Подвести итог

Эта статья знакомит вас с пятью ловушками неправильного использования пулов потоков, включая ненормальное исчезновение в пуле потоков, неправильные настройки стратегии принятия решения о пуле потоков, повторное создание пулов потоков, приводящее к переполнению памяти, использование одного и того же пула потоков для выполнения разных типов. задач и использовать ThreadLocal Проблемы несовместимости с пулами потоки и их причина проблемы и решение. Надеюсь, что этот контент будет полезен всем.

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

Я участвую в пятом этапе конкурса эссе специального тренировочного лагеря Tencent Technology Creation 2024. Приходите и разделите приз со мной!

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose