1. 死锁概念
也就是两个线程在各自拥有锁的情况下,又去尝试获取对方的锁,从而造成的一直阻塞的情况。
如下,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁
2. 如何查看是否死锁
死锁代码
public class TestLock1 { public static void main(String[] args) { new Thread(() -> { // new了一个ClassLock1对象 new LockClass().lock1(); }).start(); new Thread(() -> { // new了一个ClassLock1对象 new LockClass1().lock1(); }).start(); } } class LockClass{ public static synchronized void lock1(){ System.out.println("LockClass.lock1"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LockClass1.lock1(); } public static synchronized void lock2(){ System.out.println("lock2"); } } class LockClass1{ public static synchronized void lock1(){ System.out.println("LockClass1.lock1"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LockClass.lock1(); } }
如上代码中,由于new LockClass().lock1() new LockClass1().lock1();是两个不同类,他们之间类锁是不互斥的,也就是说第一个线程获取new LockClass().lock1() 锁不影响第二个线程获取new LockClass()1.lock1() ,因为两个线程是两把不同的锁,从而在上例中造成死锁
但如果,把上例中两个线程锁改成同一把锁,从而就不会造成如上思索问题,因为synchronzied是可重入锁
public class TestLock1 { public static void main(String[] args) { new Thread(() -> { // new了一个ClassLock1对象 new LockClass().lock1(); }).start(); new Thread(() -> { // new了一个ClassLock对象 new LockClass().lock2(); }).start(); } } class LockClass{ public static synchronized void lock1(){ System.out.println("LockClass.lock1"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LockClass1.lock1(); } public static synchronized void lock2(){ System.out.println("lock2"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } lock1(); } }
2.1 jstack
Ideal terminal中通过jps -l找到对应进程
输入jstack 进程号
jstack 7040
会得到如下死锁内容
2.2 jconsole
Win+R运行命令jconsole
3. 如何解决死锁
3.1 lock设置锁超时机制
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁
3.2 可重入锁
可重入锁解决的是同一个线程再重复获取同一把锁过程中形成死锁的问题
synchronized Reentrantlock都是可重入锁。
3.3 按顺序来获取锁
为解决如第二节中死锁的例子我们可以给锁编号,每个线程都按照锁的编号获取锁,都先调用LockClass 再调用LockClass1
public class TestLock1 { public static void main(String[] args) { new Thread(() -> { // new了一个ClassLock1对象 new LockClass().lock1(); }).start(); new Thread(() -> { // new了一个ClassLock对象 new LockClass().lock1(); }).start(); } } class LockClass{ public static synchronized void lock1(){ System.out.println("LockClass.lock1"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LockClass1.lock1(); } public static synchronized void lock2(){ System.out.println("lock2"); } } class LockClass1{ public static synchronized void lock1(){ System.out.println("LockClass1.lock1"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LockClass.lock1(); } }
3.4 如果不同线程并发存取多个表,由于存的过程会获取多个表锁资源,容易造成死锁,尽量约定以相同顺序访问表
3.5同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率
参考文献:
https://blog.csdn.net/qq_40306697/article/details/125313750