помещение
Я был немного ленив в последнее время, и у меня не так много более подробных результатов. Просто захотелось перечитатьJUC
Исходный код реализации пула потоков, давайте рассмотрим его подробнее перед этимJava
Реализация потока в , включая жизненный цикл потока, переключение состояния и переключение контекста потока и т. д. На момент написания этой статьи,JDK
Версия 11.
Реализация потоков Java
существуетПосле JDK1.2, модель многопоточности Java была определена на основе собственной модели многопоточности операционной системы. Таким образом, в текущей или будущих версиях JDK модель потоков, поддерживаемая операционной системой, в значительной степени определяет, как отображаются потоки виртуальной машины Java.Нет возможности достичь согласия по этому вопросу на разных платформах.В спецификации виртуальной машины Он также не определяет, с какой моделью многопоточности должны быть реализованы потоки Java. Модель потоков влияет только на масштаб параллелизма и стоимость операций потоков, и эти различия прозрачны для программ Java.
вести перепискуOracle Sun JDK
ИлиOracle Sun JVM
Что касается его версий для Windows и Linux, обе используютМодель потоковой передачи «один к одному»реализовано (как показано на рисунке ниже).
это одинJava
Поток сопоставляется с легковесным процессом (Light Weight Process), а облегченный поток сопоставляется с потоком ядра (Kernel-Level Thread). Поток, на который мы обычно ссылаемся, часто представляет собой легковесный процесс (или, вообще говоря, мы обычно создаем новый поток).java.lang.Thread
является «дескриптором» легковесного экземпляра процесса, потому чтоjava.lang.Thread
экземпляр будет соответствоватьJVM
тот внутриJavaThread
экземпляр, в то время какJVM
внутриJavaThread
Это следует понимать как легкий процесс). Из предыдущего расчета этого отношения сопоставления потоков мы можем узнать, что мы создаем или работаем в приложении.java.lang.Thread
Экземпляры в конечном итоге будут сопоставлены с потоками ядра системы, если мы создадим злонамеренно или экспериментально бесконечноjava.lang.Thread
например, в конечном итоге повлияет на нормальную работу системы или даже приведет к сбою системы (можно найти вWindows
Поэкспериментируйте в среде разработки, чтобы убедиться, что памяти достаточно для создания и запуска с бесконечным циклом.java.lang.Thread
пример).
Существует два типа методов планирования потоков: совместное планирование потоков и вытесняющее планирование потоков.
планирование потоков | описывать | недостаток | Преимущество |
---|---|---|---|
Совместное планирование потоков | Время выполнения потока контролируется самим потоком.После завершения выполнения операционная система активно уведомляется о переключении на другой поток. | Если поток не отдает время выполнения ЦП, это может привести к сбою всей системы. | Простая реализация, отсутствие проблем с синхронизацией потоков |
Упреждающее планирование потоков | Время выполнения каждого потока распределяется операционной системой, и переключение потоков не определяется самим потоком | Реализация относительно сложна, и операционная система должна управлять синхронизацией и переключением потоков. | Это не будет блокировкой потока, вызывающей сбой системы |
Java
Поток в конечном итоге будет сопоставлен с собственным потоком ядра системы, поэтомуJava
Планирование потоков в конечном итоге зависит от операционной системы, и текущее планирование потоков ядра основной операционной системы в основном использует вытесняющее планирование потоков. То есть можно запомнить наизусть:Потоки Java используют упреждающее планирование потоков для планирования потоков..
Многие операционные системы предоставляют концепцию приоритета потока, но из-за особенностей платформы приоритет потока в Java не соответствует приоритету системного потока на разных платформах, поэтому приоритет потока Java можно понимать только как "Приоритет рекомендации", с точки зрения непрофессионалаjava.lang.Thread#setPriority(int newPriority)
не обязательно вступает в силу,Возможно, что приоритет Java-потока будет изменен самой системой.
Переключение состояний потоков Java
Java
Состояние потока может быть изменено сjava.lang.Thread
Внутренний класс перечисленияjava.lang.Thread$State
Узнайте, что:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
Описания этих состояний суммированы следующим образом:
Переключение отношений между состояниями потокаКартина выглядит следующим образом:
Давайте проанализируем значение состояния и переключение состояний потоков Java с помощью комментариев API и нескольких простых примеров кода.
НОВЫЙ статус
Примечания к API:
/**
* Thread state for a thread which has not yet started.
*
*/
NEW,
Экземпляр резьбы не был запущен, когда состояние потока.
Только что созданный, но еще не запущенный (еще не вызванныйThread#start()
метод) экземпляра потока Java, который находится вNEW
условие.
public class ThreadState {
public static void main(String[] args) throws Exception {
Thread thread = new Thread();
System.out.println(thread.getState());
}
}
// 输出结果
NEW
ВЫПОЛНЯЕМОЕ состояние
Примечания к API:
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
Состояние потока резьбы в прогонтевом состоянии. Нить в родинельном состоянии выполняется на виртуальной машине Java, но она может выполнять другие ресурсы, ожидающие операционной системы, такие как процессор.
Когда экземпляр потока Java вызываетThread#start()
После этого вы войдетеRUNNABLE
условие.RUNNABLE
Состояние можно рассматривать как содержащее два подсостояния:READY
а такжеRUNNING
.
-
READY
: поток в этом состоянии может быть запланирован планировщиком потоков, чтобы изменить его наRUNNING
условие. -
RUNNING
: это состояние указывает на то, что поток запущен, объект потокаrun()
Инструкция, соответствующая коду в методе, выполняется процессором.
Когда экземпляр потока JavaThread#yield()
Когда метод вызывается или из-за планирования планировщиком потока, состояние экземпляра потока может быть определено с помощьюRUNNING
ВREADY
, а из состояния потокаThread#getState()
Полученный статус по-прежнемуRUNNABLE
. Например:
public class ThreadState1 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(2000);
System.out.println(thread.getState());
}
}
// 输出结果
RUNNABLE
ОЖИДАНИЕ статус
Примечания к API:
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
Состояние ожидающего потока. Поток входит в состояние ожидания, вызывая один из следующих методов: Object#wait() без тайм-аута Thread#join() без тайм-аута LockSupport.park() Поток в состоянии ожидания всегда ожидает, пока другой поток выполнит некоторую специальную обработку. Например: поток вызывает Object#wait(), затем он ожидает, пока другой поток вызовет Object#notify() или Object#notifyAll() для объекта; поток вызывает Thread#join(), затем он ожидает другой поток Поток завершается.
WAITING
данеопределенное состояние ожидания, потокам в этом состоянии не будет выделяться время выполнения ЦП. Когда поток выполняет определенные методы, он переходит в неопределенное состояние ожидания до тех пор, пока не будет явно разбужен.После пробуждения состояние потока изменяется наWAITING
изменить наRUNNABLE
Затем продолжайте.
RUNNABLE Перевести вWAITING метод (ожидание бесконечно) |
WAITING Перевести вRUNNABLE метод (пробуждение) |
---|---|
Object#wait() |
`Object#notify() |
Thread#join() |
- |
LockSupport.part() |
LockSupport.unpart(thread) |
вThread#join()
Этот метод относительно особенный. Он блокирует экземпляр потока до тех пор, пока экземпляр потока не будет выполнен. Вы можете наблюдать его исходный код следующим образом:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
видимыйThread#join()
всегда вызывается, когда экземпляр потока живObject#wait()
метод, то есть он должен выполняться после завершения потокаisAlive()
Разблокируется только в случае false (это означает, что жизненный цикл потока закончился).
на основеWAITING
Например статус:
public class ThreadState3 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
LockSupport.park();
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
LockSupport.unpark(thread);
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 输出结果
WAITING
RUNNABLE
ВРЕМЕННОЕ ОЖИДАНИЕ
Примечания к API:
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
Состояние ожидающего потока, определяющее конкретное время ожидания. Поток переходит в это состояние, вызывая один из следующих методов с заданным периодом ожидания: Thread.sleep() Object#wait() с тайм-аутом Thread#join() с тайм-аутом LockSupport.parkNanos() LockSupport.parkUntil()
TIMED WAITING
то естьограниченное состояние ожидания, это иWAITING
Аналогично, потокам в этом состоянии не будет выделяться время выполнения ЦП, но потоки в этом состоянии не нужно явно пробуждать, им просто нужно дождаться истечения периода тайм-аута.VM
Просыпайтесь, чем-то похоже на будильник в реальной жизни.
RUNNABLE Перевести вTIMED WAITING метод (ограниченное ожидание) |
TIMED WAITING Перевести вRUNNABLE метод (тайм-аут, чтобы освободить ожидание) |
---|---|
Object#wait(timeout) |
- |
Thread#sleep(timeout) |
- |
Thread#join(timeout) |
- |
LockSupport.parkNanos(timeout) |
- |
LockSupport.parkUntil(timeout) |
- |
Например:
public class ThreadState4 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//ignore
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
Thread.sleep(1000);
System.out.println(thread.getState());
}
}
// 输出结果
TIMED_WAITING
TERMINATED
ЗАБЛОКИРОВАНО
Примечания к API:
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
Это состояние указывает на то, что поток блокируется, ожидая получения блокировки монитора. Если поток заблокирован, поток ожидает входа в блокировку монитора синхронизированного блока или синхронизированного метода или повторного входа в синхронизированный блок или синхронизированный метод после вызова Object#wait().
BLOCKED
Это состояние также является состоянием блокировки, и потокам в этом состоянии не будет выделяться время выполнения ЦП. Состояние потокаBLOCKED
Когда есть две возможные ситуации:
A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method
- Поток ожидает блокировки монитора и не может войти, пока не будет получена блокировка монитора.
synchronized
кодовый блок илиsynchronized
метод, при котором блокируются все потоки процесса, ожидающие получения блокировки.
reenter a synchronized block/method after calling Object#wait()
- Проденьте X шагов в
synchronized
кодовый блок илиsynchronized
После вызова метода (в этот момент блокировка монитора была снята)Object#wait()
После того, как метод заблокирован, когда принимающий другой поток T вызывает объект блокировкиObject#notify()/notifyAll()
, но поток T еще не вышел изsynchronized
кодовый блок илиsynchronized
метод, то поток X по-прежнему заблокирован (обратите внимание наreenter, чтобы понять это, вдруг становится понятен Сценарий 2).
Для более подробного описания, пожалуйста, обратитесь к статье, которую я написал ранее:Глубокое понимание API блокировки и пробуждения, предоставляемого Object.
Вот простой пример для сценария 1 выше:
public class ThreadState6 {
private static final Object MONITOR = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(()-> {
synchronized (MONITOR){
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
//ignore
}
}
});
Thread thread2 = new Thread(()-> {
synchronized (MONITOR){
System.out.println("thread2 got monitor lock...");
}
});
thread1.start();
Thread.sleep(50);
thread2.start();
Thread.sleep(50);
System.out.println(thread2.getState());
}
}
// 输出结果
BLOCKED
Для приведенной выше сцены 2 простой пример:
public class ThreadState7 {
private static final Object MONITOR = new Object();
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
Thread thread1 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
try {
Thread.sleep(1000);
MONITOR.wait();
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
}
});
Thread thread2 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));
try {
MONITOR.notify();
Thread.sleep(2000);
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));
}
});
thread1.start();
thread2.start();
// 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,确保thread1调用了Object#wait()
Thread.sleep(1500);
System.out.println(thread1.getState());
System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
}
}
// 某个时刻的输出如下:
[2019-06-20 00:30:22]-begin...
[2019-06-20 00:30:22]-thread1 got monitor lock...
[2019-06-20 00:30:23]-thread2 got monitor lock...
BLOCKED
[2019-06-20 00:30:23]-end...
[2019-06-20 00:30:25]-thread2 releases monitor lock...
[2019-06-20 00:30:25]-thread1 exit waiting...
В сцене 2:
- Звонки потока 2
Object#notify()
После бездействия в течение 2000 миллисекунд выйдите из блока кода синхронизации и снимите блокировку монитора. - Поток 1 спит только 1000 мс перед вызовом
Object#wait()
, в этот момент он снял блокировку монитора, поэтому поток 2 успешно входит в синхронизированный блок, а поток 1 находится в комментарии API, как указаноreenter a synchronized block/method
положение дел. - Основной поток спит в течение 1500 миллисекунд только для того, чтобы поразить поток 1 в
reenter
состояние и вывести состояние своего потока, которое оказываетсяBLOCKED
условие.
Эти три пункта кажутся немного запутанными, но, прочитав несколько раз и подумав об этом, вы сможете понять.
статус ЗАВЕРШЕН
Примечания к API:
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
Конец потока, соответствующий состоянию потока, в котором поток завершился.
TERMINATED
Статус указывает на то, что поток завершен. Экземпляр потока можно запустить только один раз, точнее, он будет вызван только один раз.Thread#run()
метод,Thread#run()
После завершения выполнения метода состояние потока изменяется наTERMINATED
, что означает, что жизненный цикл потока закончился.
Возьмем простой пример:
public class ThreadState8 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 输出结果
TERMINATED
переключатель контекста
В многопоточной среде, когда состояние потока определяетсяRUNNABLE
преобразовать в неRUNNABLE
(BLOCKED
,WAITING
илиTIMED_WAITING
), контекстная информация соответствующего потока (также известная какContext
,включатьCPU
содержимое регистров и программный счетчик в определенный момент времени и т. д.) необходимо сохранить, чтобы в дальнейшем поток мог возобновиться какRUNNABLE
состояние может продолжить выполнение на основе предыдущего хода выполнения. в то время как состояние потока определяется не-RUNNABLE
Государственная записьRUNNABLE
Время состояния может включать восстановление ранее сохраненной информации о контексте потока и продолжение выполнения на основе этого. Прямо здесьПроцесс сохранения и восстановления информации о контексте потоканазывается переключением контекста (Context Switch
).
Контекстное переключение потоков приведет к дополнительной нагрузке на производительность, включая накладные расходы на сохранение и восстановление информации о контексте потока, планирование потоков.CPU
затраты времени иCPU
Накладные расходы на аннулирование содержимого кэша (код, выполняемый потоком изCPU
Значение переменной, необходимое для доступа к нему из кеша, намного быстрее, чем доступ к нему из основной памяти (RAM
) намного быстрее получить доступ к значению переменной ответа, ноПереключение контекста потока сделает недействительным содержимое кэша ЦП, к которому обращается соответствующий поток, обычно ЦП.L1 Cache
а такжеL2 Cache
, так что соответствующий поток позже переназначается на среду выполнения, которая должна снова получить доступ к переменной в основной памяти для воссозданияCPU
содержимое кеша).
существуетLinux
система, черезvmstat
Команда для просмотра количества переключений глобального контекста, например:
$ vmstat 1
дляJava
программа работает, вLinux
Система также может бытьperf
команда для мониторинга, например:
$ perf stat -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass
упоминается в ссылкеWindows
Встроенные инструменты можно использовать под системойperfmon
(по сути это диспетчер задач) для контроля переключения контекста потоков.На самом деле автор не нашел способа посмотреть переключение контекста из диспетчера задач.Поискав нашел инструмент:Process Explorer. бегатьProcess Explorer
одновременно запуститьJava
программу и проверьте ее статус:
Из-за точки останова вы можете видеть, что существует более 7000 переключений контекста работающей программы, а текущее приращение переключения контекста в одну секунду равно 26 (поскольку автор установилProcess Explorer
Данные обновляются каждую секунду).
Отслеживание состояния потока
Если проект запущен в продакшене, маловероятно, что он будет вызываться частоThread#getState()
метод для отслеживания изменений состояния потока. Сам JDK предоставляет некоторые инструменты для мониторинга состояния потока, а также некоторые облегченные инструменты с открытым исходным кодом, такие как AliArthas, вот краткое введение.
использовать jvisualvm
jvisualvm
Это инструмент мониторинга индикатора JVM с ожиданием кучи и потока, который поставляется с JDK и подходит для использования в средах разработки и тестирования. Это расположеноJAVA_HOME/bin
По каталогу.
в线程Dump
Кнопки аналогичны тем, которые будут упомянуты ниже.jstack
Команда для экспорта информации о стеке всех потоков.
использовать jstack
jstack
Это инструмент командной строки, поставляемый с JDK, его функция заключается в получении информации о стеке потоков процесса Java с указанным PID. например, один работает локальноIDEA
примерPID
11376, то просто введите:
jstack 11376
Тогда вывод консоли будет следующим:
Кроме того, если вы хотите найти конкретный процесс JavaPID
,можно использоватьjps
Заказ.
Использование JMC
JMC
то естьJava Mission Control
, это также инструмент, поставляемый с JDK, и он предоставляет больше функций, чемjvisualvm
Мощный, включая обработку MBean, просмотр состояния стека потоков, регистратор полетов и многое другое.
резюме
Понимание переключения состояний потоков Java и некоторых методов мониторинга более полезно для ежедневной разработки многопоточных программ.В случае проблем в производственной среде можно быстро найти основную причину проблемы, отслеживая информацию о стеке потоков (обычно текущий мейнстримMVC
приложение (точнее, оно должно бытьServlet
контейнер, напримерTomcat
) обрабатывать один запрос через поток. Когда запрос заблокирован, поток, который экспортирует соответствующий запрос на обработку, может в основном определить точное местоположение блокировки. Если используется очередь сообщений, напримерRabbitMQ
, блокировка потребительского потока также может быть решена с помощью аналогичной идеи).
(Конец этой статьи e-a-20200804 c-3-d)