1. 死锁概念

也就是两个线程在各自拥有锁的情况下,又去尝试获取对方的锁,从而造成的一直阻塞的情况

如下,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁

                                      

 死锁触发条件:

  •  互斥:互斥是指同一个段代码只能被一个线程访问,如上例,锁a包含的代码块被线程A访问了,便不能被线程B访问,这就是所谓的互斥
  •  循环依赖:如上线程A等待线程B持有的锁b;同时线程B等待线程A持有的锁a
  • 占有并等待:如上线程A占有锁a并等待锁b
  • 不可剥夺:线程A持有的锁a不能被线程B强行剥夺

 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();
    }
}
View Code

如上代码中,由于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();
    }
}
View Code

 

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();
    }
}
View Code

 

3.4 如果不同线程并发存取多个表,由于存的过程会获取多个表锁资源,容易造成死锁,尽量约定以相同顺序访问表

 

3.5同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率

 

4 Synchronized 死锁 vs 可重入锁

死锁:死锁参与者是两个线程 两把锁,触发条件是每个线程持有一把锁的前提下尝试获取对方持有的另一把锁

可重入锁:参与者是一个线程 一把锁,其含义是同一个线程可以多次获取同一把锁,即线程持有该锁的情况下可以再次进入同步代码块

//可重入锁实例 synchronzed锁的是方法,对应是对象锁,可以认为是对应ReetrantDemo实例对象的同一把锁
public
class ReentrantDemo { public synchronized void method1() { System.out.println("method1执行"); method2(); // 在同步方法中调用另一个同步方法 } public synchronized void method2() { System.out.println("method2执行"); } public static void main(String[] args) { ReentrantDemo demo = new ReentrantDemo(); demo.method1(); } } //死锁实例 两把不同对象的锁,两个不同的线程 public class NestedMethodDeadlock { private final Object lockA = new Object(); private final Object lockB = new Object(); public void method1() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + " 持有 lockA"); method2(); } } public void method2() { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + " 持有 lockB"); method1(); } } public static void main(String[] args) { NestedMethodDeadlock demo = new NestedMethodDeadlock(); new Thread(demo::method1, "线程1").start(); new Thread(demo::method2, "线程2").start(); } }

 

 

参考文献:

https://blog.csdn.net/qq_40306697/article/details/125313750

 

 

 

 

posted on 2023-04-11 11:35  colorfulworld  阅读(54)  评论(0)    收藏  举报