Тупик относится к двум или более вычислительным единицам (процессам, потокам или сопрограммам), все из которых ожидают, пока другая сторона прекратит выполнение для получения системных ресурсов, но ни одна из сторон не выйдет заранее, это называется тупиком.
1. Демо тупика
Формирование тупиковой ситуации делится на два аспекта: тупиковая ситуация, образованная с помощью встроенной синхронизированной блокировки, и тупиковая ситуация, реализованная с использованием явной блокировки Блокировка.Давайте рассмотрим их отдельно.
1.1 Синхронизированная версия Deadlock
public class DeadLockExample {
public static void main(String[] args) {
Object lockA = new Object(); // 创建锁 A
Object lockB = new Object(); // 创建锁 B
// 创建线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 先获取锁 A
synchronized (lockA) {
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试获取锁 B
System.out.println("线程 1:等待获取 B...");
synchronized (lockB) {
System.out.println("线程 1:获取到锁 B!");
}
}
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 先获取锁 B
synchronized (lockB) {
System.out.println("线程 2:获取到锁 B!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试获取锁 A
System.out.println("线程 2:等待获取 A...");
synchronized (lockA) {
System.out.println("线程 2:获取到锁 A!");
}
}
}
});
t2.start(); // 运行线程
}
}
Результат выполнения вышеуказанной программы выглядит следующим образом:Из приведенных выше результатов видно, что и поток 1, и поток 2 ждут друг друга, чтобы снять блокировку, что вызывает проблему взаимной блокировки.
1.2 Версия Deadlock Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadLockByReentrantLockExample {
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockA.lock(); // 加锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
System.out.println("线程 1:等待获取 B...");
lockB.lock(); // 加锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
}
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
Thread.sleep(1000);
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockB.unlock(); // 释放锁
}
}
});
t2.start(); // 运行线程
}
}
Результат выполнения вышеуказанной программы выглядит следующим образом:
2. Причины взаимоблокировки
Из вышеприведенного примера можно сделать вывод, что для генерацииТупик должен соответствовать следующим 4 условиям:
- взаимоисключающее условие: Это означает, что операционная единица (процесс, поток или сопрограмма) является эксклюзивной для выделенных ресурсов, то есть определенный ресурс блокировки может быть занят только одной операционной единицей за определенный период времени.
- Условия запроса и удержания: Это означает, что вычислительный блок удерживает по крайней мере один ресурс, но сделан новый запрос ресурса, а ресурс уже занят другими вычислительными блоками.В это время запрашивающий вычислительный блок заблокирован, но другие ресурсы, которые он получил хранятся.
- неотъемлемое условие: Относится к ресурсам, полученным операционным блоком, которые нельзя лишить, пока они не будут израсходованы.
- условие ожидания цикла: Это означает, что при возникновении взаимоблокировки должна быть круговая цепочка вычислительных блоков и ресурсов, то есть вычислительный блок ожидает ресурсы, занятые другим вычислительным блоком, а другая сторона ждет ресурсы, занятые им самим , что приводит к ситуации ожидания цикла.
Только когда вышеуказанные четыре условия выполняются одновременно, возникает проблема взаимоблокировки.
3. Инструмент устранения неполадок взаимоблокировки
Если в программе есть проблема взаимоблокировки, вы можете проанализировать и устранить неполадки с помощью любого из следующих 4 решений.
3.1 jstack
Прежде чем использовать jstack, нам нужно получить идентификатор процесса запущенной программы через jps.Метод использования следующий:«jps -l» может запрашивать все Java-программы машины, jps (инструмент состояния виртуальной машины Java) — это команда, предоставляемая Java для отображения pid всех текущих процессов Java, подходящая для простого просмотра текущей Java в linux/ платформы unix/windows Для некоторых простых случаев процессов "-l" используется для вывода идентификатора процесса и полного пути к запущенной программе (имя пакета и имя класса).
С идентификатором процесса (PID) мы можем использовать «jstack -l PID», чтобы найти проблему взаимоблокировки, как показано на следующем рисунке:jstack используется для генерации снимка потока текущего момента виртуальной машины Java, "-l" означает длинный список (long), печатающий дополнительную информацию о блокировке.
PS: Вы можете использовать jstack -help, чтобы увидеть больше инструкций по командам.
3.2 jconsole
Чтобы использовать jconsole, вам нужно открыть каталог bin JDK, найти jconsole и открыть его двойным щелчком, как показано на следующем рисунке:Затем выберите программу для отладки, как показано ниже:
Затем щелкните соединение для входа, выберите «небезопасное соединение», чтобы перейти на домашнюю страницу мониторинга, как показано на следующем рисунке:
Затем переключитесь на модуль «Поток» и нажмите кнопку «Обнаружить взаимоблокировку», как показано на следующем рисунке:
Через некоторое время будет обнаружена соответствующая информация о взаимоблокировке, как показано на следующем рисунке:
3.3 jvisualvm
jvisualvm также находится в каталоге bin JDK, который также открывается двойным щелчком:Через несколько секунд все локальные Java-программы появятся в jvisualvm, как показано на следующем рисунке:
Дважды щелкните, чтобы выбрать программу для отладки:
Щелкните мышью, чтобы войти в модуль «Поток», как показано на следующем рисунке:
Как видно из приведенного выше рисунка, когда мы переключаемся на столбец потока, информация о взаимоблокировке будет отображаться напрямую, а затем нажмите «Дамп потока», чтобы сгенерировать сведения о взаимоблокировке, как показано на следующем рисунке:
3.4 jmc
jmc, сокращение от Oracle Java Mission Control, представляет собой набор инструментов для управления, мониторинга, профилирования и устранения неполадок программ Java. Он также находится в каталоге bin JDK, и его также можно запустить двойным щелчком, как показано на следующем рисунке:Информация на домашней странице jmc выглядит следующим образом:
Затем выберите программу для проверки, щелкните правой кнопкой мыши «Запустить консоль JMX», чтобы просмотреть сведения об этой программе, как показано на следующем рисунке:
Затем нажмите «Поток» и установите флажок «Обнаружение взаимоблокировки», чтобы найти сведения о взаимоблокировке и взаимоблокировке, как показано на следующем рисунке:
4. Тупиковые решения
4.1 Анализ решения тупиковой ситуации
Далее разберем четыре условия, вызывающие взаимоблокировку, которую можно разрушить? Что нельзя уничтожить?
- Условия взаимного исключения: Характеристики системы, которые нельзя уничтожить.
- Условие запроса и удержания: может быть нарушено.
- Неотъемлемое условие: системная характеристика, которую нельзя уничтожить.
- Условие ожидания цикла: может быть нарушено.
На основе вышеприведенного анализа мы можем сделать вывод, что мы можем решить проблему взаимоблокировки, только уничтожив условие запроса и удержания или условие ожидания цикла.При выходе в онлайн мы начнем с уничтожения «условия ожидания цикла».Решить проблему взаимоблокировки .
4.2 Решение 1: блокировка последовательности
Так называемая последовательная блокировка относится к решению проблемы взаимоблокировки путем получения блокировок упорядоченным образом, что позволяет избежать создания условий ожидания цикла.
Когда мы не используем последовательные блокировки, выполнение программы может выглядеть так:Поток 1 сначала получает блокировку A, затем блокировку B, поток 2 и поток 1 выполняются одновременно, поток 2 сначала получает блокировку B, а затем блокировку A, так что обе стороны занимают свои собственные ресурсы (блокировка A и блокировка B). ) сначала. , а затем попытаться получить блокировку другой стороны, что вызывает проблему ожидания цикла и, наконец, вызывает проблему взаимоблокировки.
На данный момент нам нужно только унифицировать порядок, в котором поток 1 и поток 2 получают блокировки, то есть после того, как поток 1 и поток 2 выполняются одновременно, они оба сначала получают блокировку A, а затем получают блокировку B. поток выполнения показан на следующем рисунке:Поскольку только один поток может успешно получить блокировку A, поток, который не получил блокировку A, будет ждать, чтобы первым получить блокировку A. В это время поток, который получает блокировку A, продолжает получать блокировку B, потому что нет ни одного потока, конкурирующего за блокировку A. владение блокировкой B, затем получение Поток блокировки A будет успешно владеть блокировкой B, а затем выполнить соответствующий код, чтобы освободить все ресурсы блокировки, а затем другой поток, ожидающий получения блокировки A, может успешно получить ресурс блокировки и выполнить последующий код , так что это не проблема взаимоблокировки.
Код реализации последовательной блокировки выглядит следующим образом:
public class SolveDeadLockExample {
public static void main(String[] args) {
Object lockA = new Object(); // 创建锁 A
Object lockB = new Object(); // 创建锁 B
// 创建线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 1:等待获取 B...");
synchronized (lockB) {
System.out.println("线程 1:获取到锁 B!");
}
}
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println("线程 2:获取到锁 A!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 2:等待获取B...");
synchronized (lockB) {
System.out.println("线程 2:获取到锁 B!");
}
}
}
});
t2.start(); // 运行线程
}
}
Результат выполнения вышеуказанной программы выглядит следующим образом:Из приведенных выше результатов выполнения видно, что в программе нет проблемы взаимоблокировки.
4.3 Решение 2. Блокировка опроса
Блокировка опроса позволяет избежать взаимоблокировки, нарушая «условие запроса и удержания». Идея ее реализации состоит в том, чтобы просто попытаться получить блокировку посредством опроса. Если получение блокировки не удается, все блокировки, принадлежащие текущему потоку, освобождаются. следующий раунд, чтобы попытаться получить замок.
Реализация блокировки опроса должна использовать метод tryLock ReentrantLock, Конкретный код реализации выглядит следующим образом:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1(使用轮询锁)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 调用轮询锁
pollingLock(lockA, lockB);
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
Thread.sleep(1000);
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockB.unlock(); // 释放锁
}
}
});
t2.start(); // 运行线程
}
/**
* 轮询锁
*/
public static void pollingLock(Lock lockA, Lock lockB) {
while (true) {
if (lockA.tryLock()) { // 尝试获取锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
System.out.println("线程 1:等待获取 B...");
if (lockB.tryLock()) { // 尝试获取锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
System.out.println("线程 1:释放锁 B.");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
System.out.println("线程 1:释放锁 A.");
}
}
// 等待一秒再继续执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Результат выполнения вышеуказанной программы выглядит следующим образом:Как видно из приведенных выше результатов, в приведенном выше коде нет проблемы взаимоблокировки.
4.4 Оптимизация блокировки опроса
Хотя использование блокировок опроса может решить проблему взаимоблокировок, оно не идеально, например следующие проблемы.
4.4.1 Проблема 1: Бесконечный цикл
Вышеупомянутая упрощенная версия блокировки опроса, если есть поток, который занимает или занимает ресурсы блокировки в течение длительного времени, это приведет к тому, что блокировка опроса войдет в состояние бесконечного цикла, он будет пытаться получить ресурсы блокировки все время, которое приведет к новым проблемам, приведет к ненужным накладным расходам. Конкретные примеры приведены ниже.
Контрпример
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1(使用轮询锁)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 调用轮询锁
pollingLock(lockA, lockB);
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
Thread.sleep(1000);
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 如果此处代码未执行,线程 2 一直未释放锁资源
// lockB.unlock();
}
}
});
t2.start(); // 运行线程
}
/**
* 轮询锁
*/
public static void pollingLock(Lock lockA, Lock lockB) {
while (true) {
if (lockA.tryLock()) { // 尝试获取锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
System.out.println("线程 1:等待获取 B...");
if (lockB.tryLock()) { // 尝试获取锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
System.out.println("线程 1:释放锁 B.");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
System.out.println("线程 1:释放锁 A.");
}
}
// 等待一秒再继续执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Результат выполнения приведенного выше кода выглядит следующим образом:Из приведенных выше результатов видно, что блокировка опроса потока 1 перешла в состояние бесконечного цикла.
Оптимизировано
Ввиду описанной выше ситуации с бесконечным циклом мы можем улучшить следующие две идеи:
- Добавьте ограничение на максимальное количество раз: если блокировка не была получена после n попыток получить блокировку, считается, что получение блокировки не удалось, и опрос прекращается после выполнения стратегии отказа (стратегия отказа может быть регистрация или другие операции);
- Добавьте ограничение по максимальному времени: если блокировка не была получена после n секунд попыток получить блокировку, считается, что получение блокировки не удалось, и опрос прекращается после выполнения политики отказа.
Вы можете выбрать одну из приведенных выше стратегий для решения проблемы бесконечного цикла.С учетом стоимости реализации мы можем улучшить блокировку опроса, опрашивая максимальное количество раз.Конкретный код реализации выглядит следующим образом:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1(使用轮询锁)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 调用轮询锁
pollingLock(lockA, lockB, 3);
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
Thread.sleep(1000);
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 线程 2 忘记释放锁资源
// lockB.unlock(); // 释放锁
}
}
});
t2.start(); // 运行线程
}
/**
* 轮询锁
*
* maxCount:最大轮询次数
*/
public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
// 轮询次数计数器
int count = 0;
while (true) {
if (lockA.tryLock()) { // 尝试获取锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(1000);
System.out.println("线程 1:等待获取 B...");
if (lockB.tryLock()) { // 尝试获取锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
System.out.println("线程 1:释放锁 B.");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
System.out.println("线程 1:释放锁 A.");
}
}
// 判断是否已经超过最大次数限制
if (count++ > maxCount) {
// 终止循环
System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
return;
}
// 等待一秒再继续尝试获取锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Результат выполнения приведенного выше кода выглядит следующим образом:Из приведенных выше результатов видно, что при улучшении блокировка опроса не будет иметь проблемы с бесконечным циклом и прекратит выполнение после определенного количества попыток.
4.4.2 Проблема 2: голодание потока
Время ожидания опроса нашей блокировки опроса выше является фиксированным временем, как показано в следующем коде:
// 等待 1s 再尝试获取(轮询)锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
В особых случаях это вызовет проблему голодания потока, то есть проблему, заключающуюся в том, что блокировка опроса не может получать блокировку все время, как в следующем примере.
Контрпример
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1(使用轮询锁)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 调用轮询锁
pollingLock(lockA, lockB, 3);
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} finally {
lockB.unlock(); // 释放锁
}
// 等待一秒之后继续执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start(); // 运行线程
}
/**
* 轮询锁
*/
public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
// 循环次数计数器
int count = 0;
while (true) {
if (lockA.tryLock()) { // 尝试获取锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(100); // 等待 0.1s(获取锁需要的时间)
System.out.println("线程 1:等待获取 B...");
if (lockB.tryLock()) { // 尝试获取锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
System.out.println("线程 1:释放锁 B.");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
System.out.println("线程 1:释放锁 A.");
}
}
// 判断是否已经超过最大次数限制
if (count++ > maxCount) {
// 终止循环
System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
return;
}
// 等待一秒再继续尝试获取锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Результат выполнения приведенного выше кода выглядит следующим образом:Из приведенных выше результатов видно, что поток 1 (блокировка опроса) не получил блокировку успешно. Причина этого результата в том, что время ожидания потока 1 для каждого опроса составляет фиксированную единицу, а частота потока 2 равна то же самое. , блокировка запрашивается каждую 1 с, что приведет к тому, что поток 2 успешно получит блокировку первым, в то время как поток 1 всегда будет в ситуации «голодания». Процесс выполнения показан на следующем рисунке:
Оптимизировано
Затем мы можем преобразовать фиксированное время ожидания для блокировки опроса,Улучшено до фиксированного времени + случайное время, что позволяет избежать проблемы "голодания" блокировок опроса из-за одинаковой частоты получения блокировок. Конкретный код реализации выглядит следующим образом:
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
private static Random rdm = new Random();
public static void main(String[] args) {
Lock lockA = new ReentrantLock(); // 创建锁 A
Lock lockB = new ReentrantLock(); // 创建锁 B
// 创建线程 1(使用轮询锁)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 调用轮询锁
pollingLock(lockA, lockB, 3);
}
});
t1.start(); // 运行线程
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
lockB.lock(); // 加锁
System.out.println("线程 2:获取到锁 B!");
try {
System.out.println("线程 2:等待获取 A...");
lockA.lock(); // 加锁
try {
System.out.println("线程 2:获取到锁 A!");
} finally {
lockA.unlock(); // 释放锁
}
} finally {
lockB.unlock(); // 释放锁
}
// 等待一秒之后继续执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start(); // 运行线程
}
/**
* 轮询锁
*/
public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
// 循环次数计数器
int count = 0;
while (true) {
if (lockA.tryLock()) { // 尝试获取锁
System.out.println("线程 1:获取到锁 A!");
try {
Thread.sleep(100); // 等待 0.1s(获取锁需要的时间)
System.out.println("线程 1:等待获取 B...");
if (lockB.tryLock()) { // 尝试获取锁
try {
System.out.println("线程 1:获取到锁 B!");
} finally {
lockB.unlock(); // 释放锁
System.out.println("线程 1:释放锁 B.");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockA.unlock(); // 释放锁
System.out.println("线程 1:释放锁 A.");
}
}
// 判断是否已经超过最大次数限制
if (count++ > maxCount) {
// 终止循环
System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
return;
}
// 等待一定时间(固定时间 + 随机时间)之后再继续尝试获取锁
try {
Thread.sleep(300 + rdm.nextInt(8) * 100); // 固定时间 + 随机时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Результат выполнения приведенного выше кода выглядит следующим образом:Из приведенных выше результатов видно, что поток 1 (блокировка опроса) не будет иметь проблемы голодания потока после добавления случайного времени ожидания.
5. Резюме
Эта статья знакомит с концепцией взаимоблокировки и четырьмя условиями, которые приводят к взаимоблокировке. Взаимоблокировку можно обнаружить с помощью любого из четырех инструментов, представленных в этой статье. С точки зрения простоты использования и производительности рекомендуется использовать jconsole или jvisualvm, наконец, мы вводим два решения проблемы взаимоблокировки: последовательные блокировки и циклические блокировки.
Параллельная рекомендация оригинальной статьи
- 4 способа создания тем и подробные пояснения по их использованию!
- В чем разница между пользовательскими потоками и потоками демона в Java?
- Глубокое понимание пула потоков ThreadPool
- 7 способов создать пул потоков, настоятельно рекомендуется их использовать...
- Насколько хороша технология объединения? Я был потрясен, когда увидел сравнение между потоками и пулами потоков!
- Синхронизация потоков и блокировки в параллелизме
- Разница между синхронизированной блокировкой в этом и классе!
- Разница между volatile и синхронизированным
- Обязательно ли легкие замки быстрее, чем тяжелые замки?
- Приведет ли завершение потока таким образом к отключению службы?
- 5 решений для небезопасного потока SimpleDateFormat!
- ThreadLocal не прост в использовании? Ты бесполезен!
- Демонстрация кода переполнения памяти ThreadLocal и анализ причин!
- Признания Семафора: Я прав с ограничителем тока!
- CountDownLatch: Не сомневайтесь, подождите, пока все перегруппируются!
- CyclicBarrier: Когда все люди будут готовы, водитель может стартовать!
- Блокировочный механизм расширения синхронизированной оптимизации означает!
- 4 оптимизации в синхронизированных, сколько вы знаете?
- 4 ямы в ReentrantLock!
- Иллюстрация: Почему производительность нечестных блокировок выше?
- 4 инструмента для устранения взаимоблокировок!
- Deadlock Terminator: последовательные блокировки и блокировки опроса!
- Проблемы и решения, возникающие при использовании блокировок опроса!
Подпишитесь на официальный аккаунт «Сообщество китайского языка Java», чтобы увидеть больше интересных и полезных статей о параллельных Java.