Java Multithreading 7: Deadlock

Reprinted from: http://www.cnblogs.com/xrq730/p/4853713.html


foreword

Deadlock is written in a separate article because this is a very serious problem that must be paid attention to. This is not to overstate the risk of deadlock, although locks are usually held for a short period of time, an application as a commercial product may perform billions of lock acquisition -> release lock operations every day, as long as the A single error in an operation can lead to a deadlock in a program, and it is impossible to find all potential deadlocks even with a stress test.

 

deadlock

A classic multithreading problem.

When a thread holds a lock forever, and other threads try to acquire the lock, then they will be blocked forever, we all know that. If thread A holds lock L and wants to acquire lock M, and thread B holds lock M and wants to acquire lock L, then the two threads will wait forever, which is the simplest form of deadlock.

Deadlock monitoring and recovery from deadlock are considered in the design of the database system. If the database detects that a group of transactions is deadlocked, it will select a victim and give up the transaction. The Java virtual machine is not as powerful as the database in solving the deadlock problem. When a set of Java threads deadlocks, the two threads can never be used again, and because the two threads hold two locks, then this The two synchronized code/blocks can no longer run - unless the app is terminated and restarted.

Deadlock is a bug of design, and the problem is relatively obscure. However, the impact of deadlocks is rarely immediately apparent. The possibility of deadlocks in a class does not mean that deadlocks will occur every time, it just means that it is possible. When deadlocks occur, it's often in the worst-case scenario -- high load .

Here is a simple code that produces a deadlock and demonstrates how to analyze it as a deadlock:

copy code
public class DeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();
    
    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            Thread.sleep(2000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }
    
    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            Thread.sleep(2000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}
copy code

Note that there must be "Thread.sleep(2000)" to let the thread sleep, otherwise one thread is running, and the other thread is not running, the thread that runs first is likely to have acquired two locks in a row. Write two threads to call them separately:

copy code
public class Thread0 extends Thread
{
    private DeadLock dl;
    
    public Thread0(DeadLock dl)
    {
        this.dl = dl;
    }
    
    public void run()
    {
        try
        {
            dl.leftRight();
        }
        catch (Exception e)
        {
            e.printStackTrace ();
        }
    }
}
copy code
copy code
public class Thread1 extends Thread
{
    private DeadLock dl;
    
    public Thread1(DeadLock dl)
    {
        this.dl = dl;
    }
    
    public void run()
    {
        try
        {
            dl.rightLeft();
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
copy code

写个main函数调用一下:

copy code
public static void main(String[] args)
{
    DeadLock dl = new DeadLock();
    Thread0 t0 = new Thread0(dl);
    Thread1 t1 = new Thread1(dl);
    t0.start();
    t1.start();

    while(true);   
}
copy code

至于结果,没有结果,什么语句都不会打印,因为死锁了。下面演示一下如何定位死锁问题:

1、jps获得当前Java虚拟机进程的pid

 

2、jstack打印堆栈。jstack打印内容的最后其实已经报告发现了一个死锁,但因为我们是分析死锁产生的原因,而不是直接得到这里有一个死锁的结论,所以别管它,就看前面的部分

先说明介绍一下每一部分的意思,以"Thread-1"为例:

(1)"Thread-1"表示线程名称

(2)"prio=6"表示线程优先级

(3)"tid=00000000497cec00"表示线程Id

(4)nid=0x219c

线程对应的本地线程Id,这个重点说明下。因为Java线程是依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码,只有本地线程才是真正的线程实体。Java代码中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。Linux环境下可以使用"top -H -p JVM进程Id"来查看JVM进程下的本地线程(也被称作LWP)信息,注意这个本地线程是用十进制表示的,nid是用16进制表示的,转换一下就好了,0x219c对应的本地线程Id应该是8604。

(5)"[0x000000004a3bf000..0x000000004a3bf790]"表示线程占用的内存地址

(6)"java.lang.Thread.State:BLOCKED"表示线程的状态

解释完了每一部分的意思,看下Thread-1处于BLOCKED状态,Thread-0处于BLOCKED状态。对这两个线程分析一下:

(1)Thread-1获得了锁0x000000003416a4e8,在等待锁0x000000003416a4d8

(2) Thread-0 has obtained the lock 0x000000003416a4d8 and is waiting for the lock 0x000000003416a4e8

Since both threads are waiting to acquire the lock held by the other, they wait forever.

3. Pay attention to the use of Eclipse/MyEclipse. If this program does not click the red box on the console to Terminate it, but right-click->Run As->1 Java Application, this process will always exist. At this time You can use the taskkill command to kill processes that have not been Terminated:

 

Ways to avoid deadlocks

Since deadlock may occur, then let's talk about how to avoid deadlock.

1. Let the program acquire at most one lock at a time. Of course, in a multithreaded environment this is usually not realistic

2. Consider the order of locks clearly when designing, and minimize the number of embedded locking interactions

3. Since the deadlock is caused by two threads waiting infinitely for the lock held by the other, it is better as long as the waiting time has an upper limit. Of course, synchronized does not have this function, but we can use the tryLock method in the Lock class to try to acquire the lock. This method can specify a timeout period and return a failure message after waiting for the timeout period.


Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326899825&siteId=291194637