我们知道, 使用 synchronized 关键字可以有效的解决线程同步问题, 但是如果不恰当的使用 synchronized 关键字的话也会出问题, 即我们所说的死锁. 死锁是这样一种情形: 多个线程同时被阻塞, 它们中的一个或者全部都在等待某个资源被释放. 由于线程被无限期地阻塞, 因此程序不可能正常终止.
下面写一个死锁的例子加深理解. 先看程序, 再来分析一下死锁产生的原因:
- public class DeadLock {
- public static void main(String[] args) {
- Business business = new Business1();
- // 开启一个线程执行 Business 类中的 functionA 方法
- new Thread(new Runnable() {
- @Override
- public void run() {
- while(true) {
- business.functionA();
- }
- }
- }).start();
- // 开启另一个线程执行 Business 类中的 functionB 方法
- new Thread(new Runnable() {
- @Override
- public void run() {
- while(true) {
- business.functionB();
- }
- }
- }).start();
- }
- }
- class Business { // 定义两个锁, 两个方法
- // 定义两个锁
- public static final Object lock_a = new Object();
- public static final Object lock_b = new Object();
- public void functionA() {
- synchronized(lock_a) {
- System.out.println("---ThreadA---lock_a---");
- synchronized(lock_b) {
- System.out.println("---ThreadA---lock_b---");
- }
- }
- }
- public void functionB() {
- synchronized(lock_b) {
- System.out.println("---ThreadB---lock_b---");
- synchronized(lock_a) {
- System.out.println("---ThreadB---lock_a---");
- }
- }
- }
- }
程序结构很清晰, 没什么难度, 先看一下程序的执行结果:
---ThreadA---lock_a--- ---ThreadA---lock_b--- ---ThreadA---lock_a--- ---ThreadA---lock_b--- ---ThreadA---lock_a--- ---ThreadA---lock_b--- ---ThreadA---lock_a--- ---ThreadB---lock_b---
从执行结果来看, 线程 A 跑着跑着, 当线程 B 一跑, 啪叽一下就挂了~ 我们来分析一下原因: 从上面的代码中可以看出, 定义了一个类 Business, 该类中维护了两个锁和两个方法, 每个方法都是 synchronized 连环套, 并且使用的是不同的锁. 好了, 现在 main 方法中开启两个线程 A 和 B, 分别执行 Business 类中的两个方法. A 优先执行, 跑的很爽, 当 B 线程也开始执行的时候, 问题来了, 从执行结果的最后两行来看, A 线程进入了 functionA 方法中的第一个 synchronized, 拿到了 lock_a 锁, B 线程进入了 functionB 中的第一个 `synchronized, 拿到了 lock_b 锁, 并且两者的锁都还没释放.
接下来就是关键了: A 线程进入第二个 synchronized 的时候, 发现 lock_b 正在被 B 占用, 那没办法, 它只好被阻塞, 等呗~ 同样地, B 线程进入第二个 synchronized 的时候, 发现 lock_a 正在被 A 占用, 那没办法, 它也只好被阻塞, 等呗~ 好了, 两个就这样互相等着, 你不放, 我也不放...... 死了......
上面这个程序对于理解死锁很有帮助, 因为结构很好, 不过个人感觉这个死的还不过瘾, 因为两个线程是实现了两个不同的 Runnable 接口, 只不过调用了同一个类的两个方法而已, 因为我把要同步的方法放到一个类中了. 下面我把程序改一下, 把要同步的代码放到一个 Runnable 中, 让它一运行就挂掉......
- public class DeadLock {
- public static void main(String[] args) {
- // 开启两个线程, 分别扔两个自定义的 Runnable 进去
- new Thread(new MyRunnable(true)).start();;
- new Thread(new MyRunnable(false)).start();;
- }
- }
- class MyRunnable implements Runnable
- {
- private boolean flag; // 用于判断, 执行不同的同步代码块
- MyRunnable(boolean flag) { // 构造方法
- this.flag = flag;
- }
- @Override
- public void run()
- {
- if(flag)
- {
- while(true){
- synchronized(MyLock.lock_a)
- {
- System.out.println("--threadA---lock_a--");
- synchronized(MyLock.lock_b)
- {
- System.out.println("--threadA---lock_b--");
- }
- }
- }
- }
- else
- {
- while(true){
- synchronized(MyLock.lock_b)
- {
- System.out.println("--threadB---lock_a--");
- synchronized(MyLock.lock_a)
- {
- System.out.println("--threadB---lock_b--");
- }
- }
- }
- }
- }
- }
- class MyLock // 把两把锁放到一个类中定义, 是为了两个线程使用的都是这两把锁
- {
- public static final Object lock_a = new Object();
- public static final Object lock_b = new Object();
- }
这个死锁就厉害了, 一运行, 啪叽一下直接就挂掉了...... 看下运行结果:
--threadA---lock_a-- --threadB---lock_b--
以上是死锁的两个例子, 都比较容易理解和记忆, 主要是 "设计模式" 不太一样, 第一种结构更加清晰, 主函数中只要运行逻辑即可, 关于同步的部分全扔到 Business 中, 这个便于后期维护, 我随便把 Business 扔到哪去执行都行, 因为所有同步的东西都在它自己的类中, 这种设计思想很好.
第二种是把 Runnable 先定义好, 通过构造方法传进来不同的 boolean 类型值决定执行 run() 方法中不同的部分, 这种思路也很容易理解, 这种死锁更厉害, 两个线程直接执行相反的部分, 直接挂掉, 不给对方一点情面~
死锁就分享这么多, 如有错误之处, 欢迎指正, 我们共同进步~
来源: https://juejin.im/post/5c3759d4e51d45522c300087