一、和女朋友发生僵持怎么办?
那是一个安静祥和的一天,女盆友突然对我说:“去给我买杯奶茶”,
我:“行,你给我钱我在帮你买。”
女朋友:“你先买回来我在给你钱。”
我:“你不给钱我怎么买?”
女朋友:“说的你买回来我在给你钱嘛”
我:“我去抢?”
女朋友:“我不管”
…
结果最后发生僵持,我没得到钱,她也没得到奶茶。
结果怎么办?除非某一方让出资源呗。但这似乎是不可能的,形容两个线程互相等待对方释放资源,然后各自做各自的事情,但问题是两个线程都在苦苦等待,资源始终拿不到,与此同时,两个线程心里在想”mmp的,那个龟孙儿还不释放“,其实也就是当前线程拥有其他线程需要的资源,并且当前线程等待其他线程已拥有的资源,结果还都不会释放。
曾在某个讨论群看到一句生动形象的解释,如何向面试官回答死锁?
我:”你给我offer,我给你将死锁“
面试官:”你不给我讲死锁,我怎么给你offer?“
…
死锁
首先死锁问题是多线程下才会发生,要出现死锁问题需要满足以下条件:
-
互斥条件:一个资源每次只能被一个线程使用。
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
所以我们只要破坏死锁 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中的参数