помещение
Я был немного ленив в последнее время, и у меня не так много более подробных результатов. Просто захотелось перечитать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примерPID11376, то просто введите:
jstack 11376
Тогда вывод консоли будет следующим:

Кроме того, если вы хотите найти конкретный процесс JavaPID,можно использоватьjpsЗаказ.
Использование JMC
JMCто естьJava Mission Control, это также инструмент, поставляемый с JDK, и он предоставляет больше функций, чемjvisualvmМощный, включая обработку MBean, просмотр состояния стека потоков, регистратор полетов и многое другое.

резюме
Понимание переключения состояний потоков Java и некоторых методов мониторинга более полезно для ежедневной разработки многопоточных программ.В случае проблем в производственной среде можно быстро найти основную причину проблемы, отслеживая информацию о стеке потоков (обычно текущий мейнстримMVCприложение (точнее, оно должно бытьServletконтейнер, напримерTomcat) обрабатывать один запрос через поток. Когда запрос заблокирован, поток, который экспортирует соответствующий запрос на обработку, может в основном определить точное местоположение блокировки. Если используется очередь сообщений, напримерRabbitMQ, блокировка потребительского потока также может быть решена с помощью аналогичной идеи).
(Конец этой статьи e-a-20200804 c-3-d)