НаггетсЦзян И Джонни, пожалуйста, указывайте первоисточник для перепечатки, спасибо!
Подпишитесь на мой официальный аккаунт, чтобы получить больше галантерейных товаров~
задний план
Эта тема возникла в результате технической дискуссии, которую я вел с людьми раньше: «Как вы обнаружили взаимоблокировки и как их предотвратить и решить?» Когда я слышал этот вопрос раньше, хотя у меня были некоторые идеи, все они были недостаточно систематизированы. . Только недавно я лично столкнулся с тупиком, что я сделал такое централизованное мышление и написал следующий текст. Надеюсь поможет тем, у кого такая же проблема.
определение тупика
Во-первых, давайте взглянем на определение взаимоблокировки: «Взаимоблокировка — это явление, при котором два или более процессов блокируются из-за конкуренции за ресурсы или из-за связи друг с другом во время процесса выполнения. из них смогут двигаться вперед». Затем давайте перейдем к более формальному определению: «Каждый процесс в наборе ожидает события, которое может быть вызвано только другими процессами в этом наборе, тогда группа процессов заблокирована. "
Конкурирующими ресурсами могут быть: блокировки, сетевые подключения, события уведомлений, диск, пропускная способность и все остальное, что можно назвать «ресурсом».
взять каштан
Приведенное выше содержание может быть немного абстрактным, поэтому давайте рассмотрим пример для описания.Если в это время существует поток A, он получает блокировку в порядке: сначала блокируется a, затем блокируется b, и в то же время есть еще один поток B. Последовательность блокировки b и блокировки a получает блокировку. Как показано ниже:
Мы используем фрагмент кода для имитации описанного выше процесса:
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
public void run() {
synchronized (a) {
try {
System.out.println("now i in threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now i in threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (b) {
try {
System.out.println("now i in threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now i in threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
}
});
threadA.start();
threadB.start();
}
Результат выполнения программы следующий:
Очевидно, выполнение программы остановилось.обнаружение взаимоблокировки
Здесь я представлю два инструмента обнаружения взаимоблокировок.
1. Команда Jstack
jstack — это инструмент трассировки стека, который поставляется с виртуальной машиной Java. jstack используется для вывода информации о стеке Java для данного идентификатора процесса Java, основного файла или службы удаленной отладки.
Инструмент Jstack можно использовать для создания моментального снимка текущего момента виртуальной машины Java.снимок потокакаждый поток в текущей виртуальной машине Javaвыполняетизстек методовОсновная цель создания моментальных снимков потоков — найти причину длительных пауз в потоках, например线程间死锁
,死循环
,请求外部资源导致的长时间等待
Ждать. Когда поток приостановлен, вы можете просмотреть стек вызовов каждого потока через jstack и узнать, что не отвечающий поток делает в фоновом режиме или какие ресурсы он ожидает.
Во-первых, мы используем jps для определения номера процесса выполняемой в данный момент задачи:
jonny@~$ jps
597
1370 JConsole
1362 AppMain
1421 Jps
1361 Launcher
Можно определить, что номер процесса задачи равен 1362, а затем выполнить команду jstack для просмотра информации о стеке текущего процесса:
jonny@~$ jstack -F 1362
Attaching to process ID 1362, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 23.21-b01
Deadlock Detection:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock Monitor@0x00007fea1900f6b8 (Object@0x00000007efa684c8, a java/lang/Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock Monitor@0x00007fea1900ceb0 (Object@0x00000007efa684d8, a java/lang/Object),
which is held by "Thread-1"
Found a total of 1 deadlock.
Видно, что процесс зашел в тупик, и два потока ожидают объект Object, удерживаемый другой стороной.
2. Инструмент JConsole
Jconsole — это инструмент мониторинга, который поставляется с JDK и находится в каталоге JDK/bin. Он используется для подключения к работающей локальной или удаленной JVM, мониторинга потребления ресурсов и производительности работающих Java-приложений, построения множества диаграмм и предоставления мощного визуального интерфейса. При этом занимаемой самим сервером памяти очень мало, и можно даже сказать, что он почти не потребляет памяти.
Мы вводим команду jconsole в командной строке, автоматически появится следующее диалоговое окно, выберите процесс 1362 и нажмите «Ссылка на сайт"
После входа в обнаруженный процесс выберите вкладку «Потоки» и нажмите «Обнаружить взаимоблокировку».
Вы можете увидеть следующий экран:Видно, что процесс зашел в тупик.Все вышеприведенные примеры представляют собой взаимоблокировки, реализованные с помощью ключевого слова synchronized. Если читатель использует ReentrantLock для создания взаимоблокировки и снова использует инструмент обнаружения взаимоблокировки, взаимоблокировка также может быть обнаружена, но отображаемая информация будет более обширной. Заинтересованные читатели могут попробовать это. для них самих.
Предотвращение взаимоблокировок
Если поток может получить только одну блокировку за раз, нет взаимоблокировки порядка блокировки. Это не очень реалистично, но также очень верно (лучшее решение проблемы в том, что ее просто не бывает). Однако, что касается предотвращения тупиковой ситуации, вот следующие решения:
1. Получайте блокировки в детерминированном порядке
Если необходимо получить несколько блокировок, при проектировании необходимо учитывать порядок, в котором блокировки приобретаются перед разными потоками. Согласно приведенному выше примеру, временная диаграмма двух потоков, получающих блокировки, выглядит следующим образом:
Если вы измените время получения блокировки в это время на:
Тогда тупика никогда не будет. Для двух конкретных блокировок разработчик может попытаться получить две блокировки в порядке значения hashCode объекта блокировки, чтобы блокировки всегда получали блокировки в определенном порядке и взаимоблокировка не возникала.Проблема усложняется: если в это время существует несколько потоков, конкурирующих за разные блокировки, и выполняется простая сортировка по хэш-коду объектов блокировки (будет «ожидание цикла» просто путем сортировки по порядку хэш-кода), требования могут не выполняться. быть удовлетворены. Теперь разработчики могут использоватьАлгоритм банкира, все блокировки приобретаются в определенном порядке, что также может предотвратить возникновение взаимоблокировок.Алгоритм здесь повторяться не буду, а кому интересно, о нем можно узнать самостоятельно.
2. Откажитесь от сверхурочной работы
При использовании встроенной блокировки, предоставляемой ключевым словом synchronized, пока поток не получит блокировку, он будет ждать вечно.Однако интерфейс блокировки обеспечиваетboolean tryLock(long time, TimeUnit unit) throws InterruptedException
метод, который может ожидать блокировки в течение фиксированного периода времени, поэтому поток может активно освобождать все блокировки, которые были получены ранее, после истечения времени ожидания блокировки. Таким образом можно также очень эффективно избежать взаимоблокировок.
Как и в предыдущем примере, временная диаграмма выглядит следующим образом:
Другие формы тупика
Давайте еще раз рассмотрим определение тупика: «Взаимоблокировка относится к явлению, при котором два или более процессов блокируются из-за конкуренции за ресурсы или из-за связи друг с другом во время процесса выполнения. Если нет внешней силы, все они будут блокированы. заблокирован, не сможет двигаться вперед». Конкурирующими ресурсами в состоянии взаимоблокировки могут быть потоки в пуле потоков, соединения в пуле сетевых соединений, блокировки, предоставляемые обработчиком данных в базе данных, и т. д. — все, что можно назвать конкурирующими ресурсами.
1. Тупик пула потоков
Возьмите пример, чтобы увидеть характеристики этого тупика:
final ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<Long> f1 = executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f1");
Thread.sleep(1000);//延时
Future<Long> f2 =
executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f2");
return -1L;
}
});
System.out.println("result" + f2.get());
System.out.println("end f1");
return -1L;
}
});
В этом примере задача 1 пула потоков зависит от результата выполнения задачи 2, но пул потоков является однопоточным, то есть, если задача 1 не выполняется, задача 2 никогда не будет выполнена, что приведет к взаимоблокировке. Причины проиллюстрированы ниже:
Выполните команду jstack, вы увидите следующее:
"pool-1-thread-1" prio=5 tid=0x00007ff4c10bf800 nid=0x3b03 waiting on condition [0x000000011628c000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007ea51cf40> (a java.util.concurrent.FutureTask$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:994)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1303)
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:248)
at java.util.concurrent.FutureTask.get(FutureTask.java:111)
at com.test.TestMain$1.call(TestMain.java:49)
at com.test.TestMain$1.call(TestMain.java:37)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
Вы можете видеть, что текущий поток ожидает объекта java.util.concurrent.FutureTask.
Решение: Увеличьте количество потоков в пуле потоков или результаты задач больше не зависят друг от друга.
2. Взаимная блокировка пула сетевых подключений
Точно так же взаимоблокировка будет происходить и в пуле сетевых подключений.Предположим, что есть два потока A и B, два пула подключений к базе данных N1 и N2, а размер пула подключений равен только 1. Если поток A находится в порядке N1, первый а затем N2. Сетевое соединение получено, и поток B получает сетевое соединение в порядке: сначала N2, а затем N1, и два потока не освобождают связь, которую они уже удерживают, до завершения выполнения, поэтому это также вызывает взаимоблокировку.
// 连接1
final MultiThreadedHttpConnectionManager connectionManager1 = new MultiThreadedHttpConnectionManager();
final HttpClient httpClient1 = new HttpClient(connectionManager1);
httpClient1.getHttpConnectionManager().getParams().setMaxTotalConnections(1); //设置整个连接池最大连接数
// 连接2
final MultiThreadedHttpConnectionManager connectionManager2 = new MultiThreadedHttpConnectionManager();
final HttpClient httpClient2 = new HttpClient(connectionManager2);
httpClient2.getHttpConnectionManager().getParams().setMaxTotalConnections(1); //设置整个连接池最大连接数
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
public void run() {
try {
PostMethod httpost = new PostMethod("http://www.baidu.com");
System.out.println(">>>> Thread A execute 1 >>>>");
httpClient1.executeMethod(httpost);
Thread.sleep(5000l);
System.out.println(">>>> Thread A execute 2 >>>>");
httpClient2.executeMethod(httpost);
System.out.println(">>>> End Thread A>>>>");
} catch (Exception e) {
// ignore
}
}
});
executorService.submit(new Runnable() {
public void run() {
try {
PostMethod httpost = new PostMethod("http://www.baidu.com");
System.out.println(">>>> Thread B execute 2 >>>>");
httpClient2.executeMethod(httpost);
Thread.sleep(5000l);
System.out.println(">>>> Thread B execute 1 >>>>");
httpClient1.executeMethod(httpost);
System.out.println(">>>> End Thread B>>>>");
} catch (Exception e) {
// ignore
}
}
});
Весь процесс иллюстрируется следующим образом:
После возникновения взаимоблокировки мы используем инструмент jstack для проверки информации о текущем стеке потоков и видим следующее:
"pool-1-thread-2" prio=5 tid=0x00007faa7909e800 nid=0x3b03 in Object.wait() [0x0000000111e5d000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007ea73f498> (a org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$ConnectionPool)
at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.doGetConnection(MultiThreadedHttpConnectionManager.java:518)
- locked <0x00000007ea73f498> (a org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$ConnectionPool)
at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.getConnectionWithTimeout(MultiThreadedHttpConnectionManager.java:416)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:153)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
at com.test.TestMain$2.run(TestMain.java:79)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
"pool-1-thread-1" prio=5 tid=0x00007faa7a039800 nid=0x3a03 in Object.wait() [0x0000000111d5a000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007ea73e0d0> (a org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$ConnectionPool)
at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.doGetConnection(MultiThreadedHttpConnectionManager.java:518)
- locked <0x00000007ea73e0d0> (a org.apache.commons.httpclient.MultiThreadedHttpConnectionManager$ConnectionPool)
at org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.getConnectionWithTimeout(MultiThreadedHttpConnectionManager.java:416)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:153)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
at com.test.TestMain$1.run(TestMain.java:61)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
Конечно, здесь мы предполагаем только некоторые крайние случаи: если поток возвращается вскоре после использования пула соединений и занимает следующий пул соединений только после возврата количества соединений, взаимоблокировка не произойдет.
Суммировать
В моем понимании взаимоблокировка вызвана «двумя задачами, конкурирующими за ресурсы в неразумном порядке», поэтому, чтобы избежать взаимоблокировки, приложение должно правильно обрабатывать порядок получения ресурсов. Кроме того, иногда взаимоблокировка не сразу отражается в приложении.Обычно приложение начинает появляться медленно после того, как приложение какое-то время работало в производственной среде.В реальном процессе тестирования из-за взаимоблокировки сложно вовремя обнаружить наличие взаимоблокировки в процессе тестирования, а в производственной среде взаимоблокировка приложения возникает, часто, когда приложение находится в наихудшем состоянии — в условиях высокой нагрузки. Поэтому разработчики должны тщательно анализировать использование каждого системного ресурса в процессе разработки и разумно избегать взаимоблокировок.Кроме того, при возникновении взаимоблокировки вы также можете попробовать использовать некоторые из инструментов, упомянутых в этой статье.После тщательного анализа вы всегда можно найти в чем проблема.
Выше приведено все содержание этого письма.Если вам это нравится, пожалуйста, обратите внимание на мой публичный аккаунт~ Это самый большой стимул для меня продолжать писать, спасибо~