关于工作中死锁问题的思考与实践

关于工作中死锁问题的思考与实践

  • 1 死锁的原因分析:
  • 2 分析死锁和解决死锁问题
    • 2.1 交叉死锁代码
    • 2.2 jdk自带jconsole工具分析原因
    • 2.3 jdk自带jstack工具分析原因

死锁产生的核心原因:线程之间互相持有对方线程所等待的资源,但是一直又等不到

1 死锁的原因分析:

1.1、交叉锁

如:两个线程持有了对方向下执行所需的资源;
如:一根独木桥,两个人分别从两端走上来,如果谁都不让谁先过,持有了过桥的部分资源,最后导致的结果就是两个人都一直卡在桥上,这个现象在程序中就称之为“死锁”;

1.2、内存不够

在系统内存不足的情况下,两个线程同时执行某个任务,分别获取了50M内存,但是执行任务最小需要70M内存,此时两个线程都会等待着内存的释放;

1.3、同步问答

这个可以理解为业务级别的死锁,服务端和客户端之间交互,涉及到数据状态的转换,因为某种原因导致请求中断或者直接没收到请求,如果没有做重试机制的话,此时状态就不会变化而导致业务一直在等待;

1.4、悲观锁

数据库的select for update语句,锁表、锁行;(会造成交叉锁)

1.5、文件锁

线程获得了文件的句柄资源,但是却意外退出了,此时他还享有对文件的访问权,而其他线程若要访问该资源需要等待文件句柄资源的释放;

1.6、死循环

由于代码原因导致程序进入了死循环,导致CPU的占有率居高不下,导致系统假死,其他线程获取不了CPU的资源分配;

2 分析死锁和解决死锁问题

2.1 交叉死锁代码

package com.michael.demo;

public class CrossLockTest {
	private static final Object LOCK_1 = new Object();
	private static final Object LOCK_2 = new Object();
	private static long SLEEP_TIME = 30 * 1000;

	private void testLock1() {
		//首先synchronized是可重入锁,一个线程得到"锁A"后允许该线程再次请求该对象锁,"锁A",这就是synchronized的可重入性。
		synchronized (CrossLockTest.LOCK_1) {//
			System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_1");
			try {
				Thread.sleep(SLEEP_TIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + "休息" + (SLEEP_TIME / 1000f) + "s之后,尝试获取锁: LOCK_2");
			synchronized (CrossLockTest.LOCK_2) {
				System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_2");
				try {
					Thread.sleep(SLEEP_TIME);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void testLock2() {
		synchronized (CrossLockTest.LOCK_2) {
			System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_2");
			try {
				Thread.sleep(SLEEP_TIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + "休息" + (SLEEP_TIME / 1000f) + "s之后,尝试获取锁: LOCK_1");
			synchronized (CrossLockTest.LOCK_1) {
				System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_1");
				try {
					Thread.sleep(SLEEP_TIME);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		CrossLockTest test = new CrossLockTest();
		new Thread(test::testLock1).start();
		new Thread(test::testLock2).start();
	}
}

两个线程中执行的方法,锁1和锁2的持有是反过来的,在sleep之后形成一个互相等待的效果,大家如果觉着效果不明显,可以在main()里面的调用线程中加入死循环,让线程一直等待的效果更好理解。

代码的运行结果如下:
关于工作中死锁问题的思考与实践_第1张图片

可以看到两个线程在互相等待,期望获得对方所持有的锁。

2.2 jdk自带jconsole工具分析原因

关于工作中死锁问题的思考与实践_第2张图片 关于工作中死锁问题的思考与实践_第3张图片 关于工作中死锁问题的思考与实践_第4张图片 关于工作中死锁问题的思考与实践_第5张图片

到此,工具很明确的指出了死锁的两个线程,如果您已定位问题所在,那下面的文章你可以不用看了,若不能,还能进一步分析:

2.3 jdk自带jstack工具分析原因

jvm对于每个启动的线程都会分配一个stack栈,此时可以使用jstack工具:
关于工作中死锁问题的思考与实践_第6张图片

// jstack.exe -l 进程号
D:\dev\Java\jdk1.8.0_131\bin>jstack.exe -l 8736
关于工作中死锁问题的思考与实践_第7张图片

看到了吧 交叉锁!!!^_^

你可能感兴趣的:(JAVA,关于工作中死锁问题的思考与实践,交叉锁)