Общедоступная учетная запись WeChat «Back-end Advanced», ориентированная на совместное использование серверных технологий: Java, Golang, WEB-инфраструктура, распределенное промежуточное программное обеспечение, управление услугами и т. д.
Старый водитель научил тебя всем деньгам и довел до продвинутого уровня, я не успел объяснить и сесть в автобус!
Можно сказать, что многопоточность является одним из любимых вопросов интервьюеров.Можно сказать, что это обычное дело.Являетесь ли вы новичком или опытным водителем, я полагаю, что вы столкнетесь с некоторыми вопросами о многопоточности в процессе собеседования.проблема. Тогда я сейчас выступлю в роли интервьюера и спрошу вас:
Есть пул потоков с параметрами corePoolSize = 5, maxPoolSize = 10, длина очереди блокировки BlockingQueue 5, одновременно приходят 4 задачи, Q: Сколько потоков создаст пул потоков?
Если 4 задачи не были обработаны, а 2 задачи приходят одновременно, В: Будет ли пул потоков создавать несколько потоков или нет?
Если первые 6 задач все еще не обрабатываются, а 5 задач приходят одновременно, Q: Сколько потоков будет создано в пуле потоков или они не будут созданы?
Если вы находитесь в этой точке, пожалуйста, не паникуйте, проблема невелика.
Создание конструктора аргументов пула потоков имеет что?
Чтобы ответить на этот вопрос, нам нужно найти ответы для создания параметров пула потоков:
java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
Всего существует 7 параметров для создания пула потоков.В исходном коде и corePoolSize, и maxPoolSize не могут быть меньше 0, а количество основных потоков не может превышать максимальное количество потоков.
Позвольте мне объяснить назначение этих 7 параметров:
corePoolSize
Количество основных потоков в пуле потоков, основные потоки не будут перезапущены и останутся бездействующими, даже если никакие задачи не выполняются.
maximumPoolSize
В пуле разрешено максимальное количество потоков. Когда количество потоков достигнет corePoolSize, а очередь workQueue заполнена задачами, продолжайте создавать потоки.
keepAliveTime
Время выживания «временных потоков» после превышения corePoolSize.
unit
Единица keepAliveTime.
workQueue
Когда текущее количество потоков превысит значение corePoolSize, новая задача будет находиться в состоянии ожидания и будет сохранена в workQueue. BlockingQueue — это реализация очереди блокировки в порядке поступления. Для связанных знаний об AQS я напишу отдельный, так что следите за обновлениями.
threadFactory
Чтобы создать класс фабрики потока, мы обычно устанавливаем имя потока из threadFactory, чтобы мы могли знать, каким классом фабрики был создан поток, и могли быстро найти его.
handler
Пул потоков реализует политику отклонения.Когда количество строк достигает максимального размера пула и рабочая очередь заполнена задачами, пул потоков вызывает политику отклонения обработчика для обработки запроса.
Политики отказа по умолчанию в системе следующие:
- Abortpolicy: Чтобы отклонить политику пула потоков по умолчанию, которая напрямую бросает обработку исключения.
- DiscardPolicy: Непосредственно отказаться от обработанного.
- DiscardOldestPolicy: Отменить самую старую задачу в очереди.
- CallerRunsPolicy: назначьте задачу потоку, выполняющему в данный момент метод execute для обработки.
Мы также можем настроить стратегию отклонения, реализовав интерфейс RejectedExecutionHandler.Дружественные реализации стратегии отклонения следующие:
- Сохраняйте данные в данные и обрабатывайте их, когда система простаивает
- Данные записываются в журнал, а затем обрабатываются вручную
Теперь вернемся к вопросу в начале, и на него есть хороший ответ:
Когда пул резьбы COREPOOLSIZE = 5, нить не будет автоматически создаваться при инициализации потока, поэтому когда 4 задания приходят одновременно, выполнение метода EXECUTE создаст [4] потоки для выполнения задачи;
Первые 4 задачи не выполнены, сейчас осталось еще 2 очереди и будет создано [1] новых потоков для выполнения задач, в это время poolSize=corePoolSize, осталась еще 1 задача, и пул потоков поместит оставшиеся задачи в очередь блокировки, ожидая выполнения бездействующих потоков;
Если первые 6 задач все еще не обработаны, а 5 задач приходят одновременно, в это время нет бездействующего потока для выполнения новой задачи, поэтому пул потоков продолжает помещать эти 5 задач в очередь блокировки, но обнаруживает, что блокировка Очередь заполнена, основные потоки израсходованы, и осталась еще 1 задача. Я не знаю, что делать, поэтому пул потоков может создать только [1] «временный» поток для выполнения этого задача;
Создаваемые здесь потоки описываются как «временные», потому что они не будут существовать в пуле потоков в течение длительного времени, а их время выживания равно keepAliveTime, после чего пул потоков будет поддерживать минимальное количество потоков corePoolSize.
Почему не рекомендуется использовать Executors для создания пулов потоков?
JDK предоставляет нам класс инструментов пула потоков Executors, который имеет стратегию создания пула потоков по умолчанию, которая примерно выглядит следующим образом:
- FixedThreadPool: фиксированное количество потоков пула потоков, которые имеют одинаковый размер corePoolSize и максимальный размер пула.
- SingleThreadPool: пул потоков для одного потока.
- CachedThreadPool: начальное количество основных потоков равно 0, максимальное количество потоков равно Integer.MAX_VALUE, время существования потока составляет 60 секунд в режиме ожидания, а его очередь блокировки — SynchronousQueue, а его начальная длина равна 0, что приведет к тому, что задачи создаваться каждый раз, когда они приходят. Поток выполняется, и когда поток простаивает, ресурс потока будет освобожден, когда время выживания истекло.
- ScheduledThreadPool: создает пул потоков фиксированной длины и поддерживает синхронизированное и периодическое выполнение задач, аналогично Timer.
Хотя использовать класс инструментов Executors очень удобно, я все же не рекомендую вам использовать приведенную выше стратегию создания пула потоков по умолчанию.Руководство по разработке Alibaba также не позволяет использовать Executors для создания пулов потоков.Ищем волну ответов из исходного кода JDK:
java.util.concurrent.Исполнители:
// FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// CachedThreadPool
public static ExecutorService newCachedThreadPool() {
// 允许创建线程数为Integer.MAX_VALUE
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
// 允许创建线程数为Integer.MAX_VALUE
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public LinkedBlockingQueue() {
// 允许队列长度最大为Integer.MAX_VALUE
this(Integer.MAX_VALUE);
}
Как видно из исходного кода JDK, классы инструментов Executors не подлежат ремонту для упаковки некоторых конкретных параметров и предоставляют нам некоторый подход к вызову, мы не можем гибко заполнять параметры.Стратегия слишком простая, не дружелюбная.
Максимальное количество потоков в CachedThreadPool и ScheduledThreadPool — Integer.MAX_VALUE. Если потоки создаются бесконечно, возникнет исключение OOM.
LinkedBlockingQueue — это неограниченная очередь FIFO на основе связанного списка. Размер по умолчанию — Integer.MAX_VALUE. Таким образом, длина очереди блокировки FixedThreadPool и SingleThreadPool — Integer.MAX_VALUE. Если в это время очередь бесконечно накапливается, это вызовет Исключение ООМ.