重回多线程、如何向女朋友解释死锁?

一、和女朋友发生僵持怎么办?

那是一个安静祥和的一天,女盆友突然对我说:“去给我买杯奶茶”,
我:“行,你给我钱我在帮你买。”
女朋友:“你先买回来我在给你钱。”
我:“你不给钱我怎么买?”
女朋友:“说的你买回来我在给你钱嘛”
我:“我去抢?”
女朋友:“我不管”

结果最后发生僵持,我没得到钱,她也没得到奶茶。

结果怎么办?除非某一方让出资源呗。但这似乎是不可能的,形容两个线程互相等待对方释放资源,然后各自做各自的事情,但问题是两个线程都在苦苦等待,资源始终拿不到,与此同时,两个线程心里在想”mmp的,那个龟孙儿还不释放“,其实也就是当前线程拥有其他线程需要的资源,并且当前线程等待其他线程已拥有的资源,结果还都不会释放。

曾在某个讨论群看到一句生动形象的解释,如何向面试官回答死锁?

我:”你给我offer,我给你将死锁“

面试官:”你不给我讲死锁,我怎么给你offer?“

死锁

首先死锁问题是多线程下才会发生,要出现死锁问题需要满足以下条件:

  1. 互斥条件:一个资源每次只能被一个线程使用。

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

所以我们只要破坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。

我们知道了死锁是两个甚至多个线程被永久阻塞时的情况,先编写一个简单的程序,它将会引起死锁发生,然后再说如何分析它。

public class ThreadDemo {
    static class ThreadObject_01{}
    static class ThreadObject_02{}

    public static void main(String[] args) throws InterruptedException {
        Object o1 =new ThreadObject_01();
        Object o2 =new ThreadObject_02();

        System.out.println("o1对象地址"+ ObjectAddressUtil.getAddresses(o1));
        System.out.println("o2对象地址"+ ObjectAddressUtil.getAddresses(o2));
        new Thread( ()->{
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+" 持有o1"+o1);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+" 持有o2"+o2);
                }
            }
        },"Thread-1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread( ()->{
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+" 持有o2"+o2);

                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+" 持有o1"+o1);
                }
            }
        },"Thread-2").start();

    }

}

由于后续分析需要获取对象的内存地址,所以借助ObjectAddressUtil来完成,在文章后面会给出。

运行这段代码后,首先输出两个对象的内存地址,接着线程尝试去拿到锁,但是两个线却程迟迟不能结束,这是因为首先Thread-1通过synchronized锁住o1对象,然后在尝试通过synchronized锁住o2之前,o2已经被Thread-2获取了,自己获取不到了,但是Thread-2却也尝试获取已被拥有的资源,最终发生死锁,

o1对象地址0x76d07d7b8
o2对象地址0x76d07ff68
Thread-1 持有o1ThreadDemo$ThreadObject_01@40c8e07d
Thread-2 持有o2ThreadDemo$ThreadObject_02@78a35df7

分析工具

JVM 提供了一些工具可以来帮助诊断死锁的发生,也就是jstack,可用于导出 Java 应用程序的线程堆栈,包含加锁信息,例如每个线程持有了哪些锁信息,在哪些栈帧中获得这些锁,以及被阻塞的线程正在等待获取哪一个锁。-l 选项用于打印锁的附加信息。

从以下部分可以看出两个线程的运行状态。

jstack -l 25986
2020-06-26 11:42:33
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x00007f2a1800c800 nid=0x733b waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Thread-2" #13 prio=5 os_prio=0 tid=0x00007f2a18317000 nid=0x736d waiting for monitor entry [0x00007f29abefd000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at ThreadDemo.lambda$main$1(ThreadDemo.java:37)
	- waiting to lock <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	- locked <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	at ThreadDemo$$Lambda$2/1078694789.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Attach Listener" #12 daemon prio=9 os_prio=0 tid=0x00007f29c4001000 nid=0x736c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Thread-1" #11 prio=5 os_prio=0 tid=0x00007f2a18314800 nid=0x7361 waiting for monitor entry [0x00007f29d816c000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at ThreadDemo.lambda$main$0(ThreadDemo.java:25)
	- waiting to lock <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	- locked <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	at ThreadDemo$$Lambda$1/990368553.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x00007f2a1828e000 nid=0x735f runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C1 CompilerThread3" #9 daemon prio=9 os_prio=0 tid=0x00007f2a18278800 nid=0x735e waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread2" #8 daemon prio=9 os_prio=0 tid=0x00007f2a18276800 nid=0x735d waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread1" #7 daemon prio=9 os_prio=0 tid=0x00007f2a18271000 nid=0x735c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread0" #6 daemon prio=9 os_prio=0 tid=0x00007f2a1826f000 nid=0x735b waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=0 tid=0x00007f2a1826c800 nid=0x735a runnable [0x00007f29ef797000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	- locked <0x000000076d0f9db8> (a java.io.InputStreamReader)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	- locked <0x000000076d0f9db8> (a java.io.InputStreamReader)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

   Locked ownable synchronizers:
	- None

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f2a18209000 nid=0x7352 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f2a181d8000 nid=0x734f in Object.wait() [0x00007f29efcfb000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076cf88ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	- locked <0x000000076cf88ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

   Locked ownable synchronizers:
	- None

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f2a181d3800 nid=0x734e in Object.wait() [0x00007f29efdfc000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076cf86c00> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x000000076cf86c00> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
	- None

"VM Thread" os_prio=0 tid=0x00007f2a181c9800 nid=0x734c runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f2a18021800 nid=0x7344 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f2a18023800 nid=0x7345 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f2a18025000 nid=0x7346 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f2a18027000 nid=0x7347 runnable 

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007f2a18028800 nid=0x7348 runnable 

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007f2a1802a800 nid=0x7349 runnable 

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007f2a1802c000 nid=0x734a runnable 

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007f2a1802e000 nid=0x734b runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f2a18290800 nid=0x7360 waiting on condition 

JNI global references: 316

好吧,这似乎是有点多,不知道看哪了,来一段一段分析一下,其实我们只要找到线程名为Thread-1、Thread-2的地方就行了。

下面这段信息显示了Thread-2正在等待0x76d07d7b8的锁,对象是ThreadDemo$ThreadObject_01,同时他已经拿到了0x76d07ff68的锁,对象是ThreadDemo$ThreadObject_02

"Thread-2" #13 prio=5 os_prio=0 tid=0x00007f2a18317000 nid=0x736d waiting for monitor entry [0x00007f29abefd000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at ThreadDemo.lambda$main$1(ThreadDemo.java:37)
	- waiting to lock <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	- locked <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	at ThreadDemo$$Lambda$2/1078694789.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

而Thread-1是与上面正好相反的。

"Thread-1" #11 prio=5 os_prio=0 tid=0x00007f2a18314800 nid=0x7361 waiting for monitor entry [0x00007f29d816c000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at ThreadDemo.lambda$main$0(ThreadDemo.java:25)
	- waiting to lock <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	- locked <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	at ThreadDemo$$Lambda$1/990368553.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

在最底部有这样几段信息,这个标题告诉我们(Found one Java-level deadlock),找到一个Java死锁信息,这两个线程名是Thread-2、Thread-1。

Found one Java-level deadlock:
=============================
"Thread-2":
  waiting to lock monitor 0x00007f29cc0062c8 (object 0x000000076d07d7b8, a ThreadDemo$ThreadObject_01),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 0x00007f29cc002178 (object 0x000000076d07ff68, a ThreadDemo$ThreadObject_02),
  which is held by "Thread-2"

Java stack information for the threads listed above:
===================================================
"Thread-2":
	at ThreadDemo.lambda$main$1(ThreadDemo.java:37)
	- waiting to lock <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	- locked <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	at ThreadDemo$$Lambda$2/1078694789.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"Thread-1":
	at ThreadDemo.lambda$main$0(ThreadDemo.java:25)
	- waiting to lock <0x000000076d07ff68> (a ThreadDemo$ThreadObject_02)
	- locked <0x000000076d07d7b8> (a ThreadDemo$ThreadObject_01)
	at ThreadDemo$$Lambda$1/990368553.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

死锁解决方案

有一种技术可以检测死锁和从死锁中恢复过来,也就是显式使用Lock类的tryLock功能来代替内置锁机制。我们知道,使用内置锁时,只要没有获得锁,就会永远等待下去,而显式锁则可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息。对于后面没有获取到锁时改怎么做,我们都可以控制。

获取对象地址

对JVM不太了解,这个办法是参考https://stackoverflow.com/questions/8820164/is-there-a-way-to-get-a-reference-address的。

public class ObjectAddressUtil
{

    public static String getAddresses(Object... objects)
    {
        StringBuffer sb = new StringBuffer();
        sb.append("0x");
 
        boolean is64bit = Integer.parseInt(System.getProperty("sun.arch.data.model")) == 32 ? false : true;
        Unsafe unsafe = getUnsafe();
        long last = 0;
        int offset = unsafe.arrayBaseOffset(objects.getClass());
        int scale = unsafe.arrayIndexScale(objects.getClass());
        switch (scale)
        {
            case 4:
                long factor = is64bit ? 8 : 1;
                final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
                sb.append(Long.toHexString(i1));
                last = i1;
                for (int i = 1; i < objects.length; i++)
                {
                    final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
                    if (i2 > last)
                        sb.append(", +" + Long.toHexString(i2 - last));
                    else
                        sb.append(", -" + Long.toHexString(last - i2));
                    last = i2;
                }
                break;
            case 8:
                throw new AssertionError("Not supported");
        }
        return sb.toString();
    }

    private static Unsafe getUnsafe()
    {
        try
        {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        }
        catch (Exception e)
        {
            throw new AssertionError(e);
        }
    }
}  

怎么获取pid?

从上面使用jstack命令可以发现,我们需要给定pid,他才能正常工作,在Linux下,我们可以使用ps命令查找,但是对于java进程,我想使用jps可能更好一点,当然jps也是java提供的命令,而非操作系统,位于jdk/bin下。

hxl@hxl-PC:~$ jps
10899 Jps
29495 Launcher
29498 ThreadDemo
21098 Main
hxl@hxl-PC:~$ 

当然他有很多参数,如下
-q:只输出进程 ID
-m:输出传入 main 方法的参数
-l:输出完全的包名,应用主类名,jar的完全路径名
-v:输出jvm参数
-V:输出通过flag文件传递到JVM中的参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值