【JavaEE】死锁的成因和解决方案

文章目录

  • 1.可重入锁和不可重入锁
    • 1.1可重入锁
    • 1.2不可重入锁
    • 1.3解决方案
  • 2.两个线程两把锁
    • 2.1问题
    • 2.2解决方案
  • 3.多个线程多把锁
    • 3.1问题
    • 3.2解决方案

1.可重入锁和不可重入锁

且看下面这段代码:

public class ReeantrantAndUnReeantrant {
    public static Object locker = new Object();
    public static void main(String[] args) {
        synchronized (locker) {
            synchronized (locker) {
                System.out.println("ABC");
            }
        }
    }
}

会不会打印ABC?

执行结果:
【JavaEE】死锁的成因和解决方案_第1张图片
结果打印了ABC,为什么呢?

在java中synchronized是可重入锁。那到底什么是可重入锁?

1.1可重入锁

可重入锁是当一个线程获取一个锁多次,这个线程仍然可以拿到这个锁。
【JavaEE】死锁的成因和解决方案_第2张图片
【JavaEE】死锁的成因和解决方案_第3张图片

1.2不可重入锁

不可重入锁跟可重入锁相反,一个线程拿到一个锁后,后面再尝试拿锁是拿不了的。
【JavaEE】死锁的成因和解决方案_第4张图片

【JavaEE】死锁的成因和解决方案_第5张图片

javasychronized是可重入锁,所以上面的代码能跑起来,但在其他语言中就不一定了。
如果synchronized不是可重入锁,上面的代码就会出问题,会出现死锁。

1.3解决方案

由于javasynchronized是可重入锁,所以没有需要解决的问题。

2.两个线程两把锁

2.1问题

两个线程两把锁,就算是可重入锁也会导致死锁。
如下图:
t1拿了locker1t2拿了locker2
t1尝试获取locker2t2尝试获取locker1
这就会导致t1一直获取不到locker2t2获取不到locker1
t1t2就这么僵着,谁也拿不到谁的锁。
【JavaEE】死锁的成因和解决方案_第6张图片

代码如下:

public class TwoThreadTwoLock {
    public static Object locker1 = new Object();
    public static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("t2");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

运行结果:
【JavaEE】死锁的成因和解决方案_第7张图片

2.2解决方案

规定获取锁的顺序,比如规定每个线程都先获取locker1再获取locker2

synchronized(locker1) {
	synchronized(locker2) {
		...
	}
}

对上面的代码修改后:

public class TwoThreadTwoLock {
    public static Object locker1 = new Object();
    public static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t2");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

执行结果:
【JavaEE】死锁的成因和解决方案_第8张图片

3.多个线程多把锁

3.1问题

如下图:
每个线程依赖两个锁,只有拿到这两个锁,线程才能执行代码。
t1依赖locker1locker2      ~~~~     t2依赖locker1locker5
t3依赖locker2locker3      ~~~~     t4依赖locker4locker5
t5依赖locker3locker4

【JavaEE】死锁的成因和解决方案_第9张图片
同时拿起了一把锁:
【JavaEE】死锁的成因和解决方案_第10张图片
【JavaEE】死锁的成因和解决方案_第11张图片
现在每个线程都有了一把锁,但是还需要另外一把,所以各个线程又去获取第二把锁。
【JavaEE】死锁的成因和解决方案_第12张图片
现在每个线程又陷入了一个尴尬的局面,每个线程都要去获取另一把锁,但是每个线程又获取不到。

  • 可以看到多个线程和多个锁之间也是会出现死锁的。

3.2解决方案

跟两个线程两个锁出现死锁一样,还是要约定获取锁的顺序,
把锁编号,规定先从小的锁开始获取还是从大的锁开始获取。

举个例子:
我们规定从编号小的锁开始获取锁。
t1依赖locker1locker2,所以先获取locker1再获取locker2

synchronized(locker1) {
	synchronized(locker2) {
		
	}
}

t2依赖locker1locker5,所以先获取locker1再获取locker5

synchronized(locker1) {
	synchronized(locker5) {
		
	}
}

拿上面的例子来说,规定先获取小的锁再获取大的锁。
【JavaEE】死锁的成因和解决方案_第13张图片

拿锁后:
【JavaEE】死锁的成因和解决方案_第14张图片
第二次拿锁:【JavaEE】死锁的成因和解决方案_第15张图片
t4拿到了两个锁:
【JavaEE】死锁的成因和解决方案_第16张图片
t4代码执行完毕,释放锁:
【JavaEE】死锁的成因和解决方案_第17张图片
各个线程继续拿锁,以此往复:
【JavaEE】死锁的成因和解决方案_第18张图片
最后各个线程都可以执行完毕。

你可能感兴趣的:(JavaEE,java-ee,java,多线程)