简单来说,A线程持有m对象锁,想要获得n对象锁,B线程持有n对象锁,想要获得m对象锁。(因为n对象锁已经被B线程持有了,所以A线程获取不到,程序执行不下去,自己的m对象锁也无法释放,同理m对象锁也是如此)
synchronized 实现个死锁public class MyLock2 {
public static void main(String[] args) {
//自己随便创建个对象.....
Person p1 = new Person();
Person p2 = new Person();
new Thread(()->{
//获取p1对象锁
synchronized (p1) {
System.out.println(Thread.currentThread().getName() + "-->获取到p1");
try {
//不释放p1的情况下,睡眠2秒,再获取p2对象锁(意图是让另外个线程有时间先获取p2)
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (p2) {
System.out.println(Thread.currentThread().getName() + "-->获取到p2");
}
}
},"线程1号").start();
try {
//睡眠0.5秒,让 线程1 先执行
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
//获取p2对象锁
synchronized (p2) {
System.out.println(Thread.currentThread().getName() + "-->获取到p2");
synchronized (p1) {
System.out.println(Thread.currentThread().getName() + "-->获取到p1");
}
}
},"线程2号").start();
}
}
执行之后,可以看到输出,但是程序一直不结束。
线程1号-->获取到p1 线程2号-->获取到p2
此时就发生了死锁。
查看线程日志,分析死锁。- windows 平台下 打开cmd 输入
tasklist | findstr java //类似于linux下的 grep命令
列出所有关于java的进程 , 会看到两三个关于java的进程。
输出结果类似于 java.exe 13705Console 1 53312k java.exe 321Console 1 15382k java.exe 13705Console 1 3253219k
一个的idea 的,一个是我们运行程序的。用他们的pid(进程id)导入日志。
如:321是我们程序的pid,导出日志。放到d盘根目录下了。
jstack -l 321 >d:321.txt
去d盘根目录下找到文件。打开。查看日志。
省略...... Found one Java-level deadlock: ============================= "线程2号": waiting to lock monitor 0x00000000031ba658 (object 0x00000000d5ebb930, a com.zcc.reflect_practise.Person), which is held by "线程1号" "线程1号": waiting to lock monitor 0x00000000031bba48 (object 0x00000000d5ebb948, a com.zcc.reflect_practise.Person), which is held by "线程2号" Java stack information for the threads listed above: =================================================== "线程2号": at com.zcc.thread_practise.JUC.AQS.MyLock.MyLock2.lambda$main$1(MyLock2.java:46) - waiting to lock <0x00000000d5ebb930> (a com.zcc.reflect_practise.Person) - locked <0x00000000d5ebb948> (a com.zcc.reflect_practise.Person) at com.zcc.thread_practise.JUC.AQS.MyLock.MyLock2$$Lambda$2/931919113.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "线程1号": at com.zcc.thread_practise.JUC.AQS.MyLock.MyLock2.lambda$main$0(MyLock2.java:29) - waiting to lock <0x00000000d5ebb948> (a com.zcc.reflect_practise.Person) - locked <0x00000000d5ebb930> (a com.zcc.reflect_practise.Person) at com.zcc.thread_practise.JUC.AQS.MyLock.MyLock2$$Lambda$1/1828972342.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
通过日志我们很容易看到,发现了一个死锁(Found one Java-level deadlock:)
"线程2号": waiting to lock monitor 0x00000000031ba658 (object 0x00000000d5ebb930, a com.zcc.reflect_practise.Person), which is held by "线程1号"
这一句是:(线程名称)“线程2号”等待进入monitor获取0x00000000031ba658 这个对象锁。(这个object的地址是0x00000000d5ebb930)which引导的定语从句,这个对象锁被“线程1号”持有。 同理,下一句的意思就不难得出了。所以就互相想获得对方线程持有的对象锁,但是自己的持有的对象锁又不释放。
从下面的日志从我们可以看出,发生死锁的地方,一个在代码的46行,一个在29行。
(为啥总是把锁称为对象锁呢?因为每个对象都有一把锁,这个锁保存在对象头中,对象头包含class point 和mark word 。其中class point 指向对象类型所在方法区中的Class信息。而mark word 有点复杂,简单来说,就是一块空间(包含许多东西),这个空间存放着 持有这个对象的线程local record 的地址【对象头有个空间,空间里有个地址,地址指向线程的local record】,而这个 线程 在虚拟机栈里开辟的空间,中间有一部分叫做local record,先把对象的mark word 复制到local record中,再将local record 中的owner指针,指向mark word ,就实现了对象到线程的双向绑定,所以该线程就获得了这个对象锁。汗、扯远了)
题外话:monitor 监视器。是由synchronized关键字经过编译后生成的,monitor enter 和 monitor exit ,而monitor 在操作系统层面是由mutex lock 实现的。有兴趣的可以看看 synchronized的底层实现。



