content
- Condition and Avoidance of Deadlock
- deadlock recovery
- signal loss lock
- Nested Monitor Deadlock
- thread starvation
- livelock
Condition and Avoidance of Deadlock
To generate a deadlock, all of the following conditions must be met
- Mutual exclusion of resources: The resource must be exclusive, that is, the resource can only be occupied by one thread
- Resources cannot be snatched: when the thread occupying the resource does not actively release the resource, other threads cannot obtain the resource
- Occupy and wait for resources: When a thread wants to acquire another resource, if this resource is occupied by other threads, it needs to wait for other threads to release this resource, and this thread does not release the resources occupied by itself.
Circular waiting for resources: Each thread is requesting to wait for other resources and does not actively release its own resources. The requests of these threads form a closed loop.
T1 occupies resource A and waits for resource B to be released by T2 and does not release resource A by itself
T2 occupies resource B and waits for resource C to be released by T3 and does not release resource B by itself
T3 occupies resource C and waits for resource D to be released by T4 and does not release resource C by itself
T4 occupies resource D and waits for resource A to be released by T1 and does not release resource D by itself
To avoid deadlock, just remove any of the above conditions
Ways to avoid deadlock
Lock coarsening: For example, in the philosophers problem, chopsticks can only be used exclusively. Each philosopher goes to get chopsticks at the same time, and then the philosophers all pick up the chopsticks in their left hand at the same time, but the chopsticks in their right hand are taken by the next door. The philosopher is holding the left hand, and then they are all waiting for the philosopher next door to put down the chopsticks in their left hand, and as a result, no one puts it down and it forms a deadlock.
The above is equivalent to Philosopher A, Philosopher B, Philosopher C obtaining Chopsticks A, Chopsticks B, Chopsticks C, which is equivalent to Thread A, Thread B, Thread C obtaining Lock-A, Lock-B, Lock-C!
I "roughened" this lock so that we can make only one philosopher take the chopsticks at a time, so that at least one philosopher gets the left and right chopsticks, so that there will not be a situation of circularly waiting for resources.
In fact, Lock is used to replace Lock-A, Lock-B, and Lock-C, so that only one thread can acquire resources at the same time, which can effectively avoid the occurrence of deadlocks, but the disadvantage is also obviously that it reduces the possibility of concurrency Sex, leading to waste of resources!
lock ordering
It is still a philosopher's problem. We add a sequence number to the chopsticks, chopsticks A (1), chopsticks B (2), and chopsticks C (3). When we take the chopsticks, we have to judge whether the chopsticks on the left hand have a small serial number or the right hand has a small serial number. , which chopstick should we take first.
The following scenarios may occur
Philosopher A first picked up chopsticks A (1) and then picked up chopsticks C (3), then philosopher B found that the number of chopsticks A in his right hand was smaller, but it was already obtained by philosopher A, so he would not take chopsticks B(2), then Philosopher C finds that the number of chopsticks B(2) in his right hand is smaller than that of chopsticks C(3) in his left hand, but he needs to wait for Philosopher A to put down chopsticks C(3) before he can eat.
There are many arrangements of the above philosophers holding chopsticks, but since the chopsticks need to be picked up according to the serial number of the left and right chopsticks (the serial number of each philosopher's left-hand chopsticks is not greater than that of the right-hand chopsticks, nor the serial number of each philosopher's right-hand chopsticks) larger than the left hand) so there will be no circular wait.
Use tryLock(long, TimeUnit) to apply for a lock. When a thread applies for a resource after a certain time limit, it will give up applying for the lock, so that it will not cause the scene of cyclically waiting for resources.
For example, in the Philosopher's Problem, if each philosopher has got the chopsticks in his right hand, the left hand is waiting for the next door to put down the chopsticks, but if he can't wait for the chopsticks within a certain period of time, he will give up waiting and put down the chopsticks in his right hand.
try{ leftLock.tryLock() }catch(interruptedException e){ rightLock.unLock(); ...... } ....
deadlock recovery
Locks applied for by using internal locks or Lock.lock() in java and the resulting deadlocks are unrecoverable, and these deadlocks can only be removed by restarting the virtual machine. But if the thread is applying for a lock through Lock.lockInterruptibly(), then the deadlock may be broken by calling thread.interrupt().
public class DeadlockDetector extends Thread {
static final ThreadMXBean tmb = ManagementFactory.getThreadMXBean();
/**
* 检测周期(单位为毫秒)
*/
private final long monitorInterval;
public DeadlockDetector(long monitorInterval) {
super("DeadLockDetector");
setDaemon(true);
this.monitorInterval = monitorInterval;
}
public DeadlockDetector() {
this(2000);
}
public static ThreadInfo[] findDeadlockedThreads() {
long[] ids = tmb.findDeadlockedThreads();
return null == tmb.findDeadlockedThreads() ?
new ThreadInfo[0] : tmb.getThreadInfo(ids);
}
public static Thread findThreadById(long threadId) {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getId() == threadId) {
return thread;
}
}
return null;
}
public static boolean interruptThread(long threadID) {
Thread thread = findThreadById(threadID);
if (null != thread) {
thread.interrupt();
return true;
}
return false;
}
@Override
public void run() {
ThreadInfo[] threadInfoList;
ThreadInfo ti;
int i = 0;
try {
for (;;) {
// 检测系统中是否存在死锁
threadInfoList = DeadlockDetector.findDeadlockedThreads();
if (threadInfoList.length > 0) {
// 选取一个任意的死锁线程
ti = threadInfoList[i++ % threadInfoList.length];
Debug.error("Deadlock detected,trying to recover"
+ " by interrupting%n thread(%d,%s)%n",
ti.getThreadId(),
ti.getThreadName());
// 给选中的死锁线程发送中断
DeadlockDetector.interruptThread(ti.getThreadId());
continue;
} else {
Debug.info("No deadlock found!");
i = 0;
}
Thread.sleep(monitorInterval);
}// for循环结束
} catch (InterruptedException e) {
// 什么也不做
;
}
}
}
By calling the Management.ThreadMXBean.findDeadlockedThreads()
method, you can detect the deadlocked threads in the virtual machine, and then interrupt the deadlocks of these threads to achieve deadlock recovery.
Because the cause of deadlock is uncontrollable, the operability of deadlock recovery is not strong, and new problems such as livelock may even occur during the interruption of deadlock.
signal loss lock
A typical example of signal loss lock is that the waiting thread does not judge the protection condition before executing Object.wait()/Condition.await(), and the protection condition may actually be established at this time, but there may be no other threads after that Update the shared variable involved in the corresponding protection condition to make it established and notify the waiting thread, which keeps the waiting thread in the waiting state, so that its task has been unable to progress.
Nested Monitor Deadlock
Nested locks may result in a liveness failure in which a thread cannot be notified of a wake-up waiting thread, which is known as a nested monitor deadlock.
public class NestedMonitorDeadlockDemo {
static Object lockA = new Object();
static Object lockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (lockA){
synchronized (lockB){
try {
lockB.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
synchronized (lockA){
synchronized (lockB){
lockB.notifyAll();
}
}
}
};
}
}
As shown in the above code, lockB.wait() in the t1 thread means that the t1 thread releases lockB, but lockA will not be released by it. In this case, t2 will never be able to acquire lockA and thus will not execute lockB.notifyAll(). In this case, t1 will It will never be woken up, and the t2 thread will always be stuck in the acquisition of lockA.
Nested monitor deadlock generally does not appear as "clearly" as in the above code, but generally occurs when using some APIs, using a blocking queue to simulate a message queue, as shown below
Based on the deep disassembly of the source code of the getMsg method and the setMsg method in the above figure, the usage of these two locks can be summarized as follows
==getMsg==
sychronized(NesredMonitorDeadLocalDemo.class){
lock.lockInterruptibly();
while(条件){
notEmpty.await();
}
notFull.signal();
lock.unlock();
}
==setMsg==
sychronized(NesredMonitorDeadLocalDemo.class){
lock.lockInterruptibly();
while(条件){
notFull.await();
}
notEmpty.signal();
lock.unlock();
}
After dismantling, it is found that this is a classic nested monitor deadlock. When a thread executes getMsg blocking, it will release the explicit lock but not the outermost inner lock, and another thread will access the setMsg The internal lock will never be acquired.
thread starvation
Thread starvation refers to a liveness failure in which a thread has been unable to obtain the resources it needs and its tasks have been unable to progress.
livelock
The thread has been running, but the task it is executing has never progressed, it has always owned the thread but has not released it and does some meaningless things.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。