Java并发-死锁

  • 2020 年 2 月 18 日
  • 筆記

一、死锁的简单概念

 所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无其余方法作用,它们都将无法推进下去。  网友们有一个生动形象的比方:两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不服,为什么让我先退出去,我还想先过去呢,于是就僵持不下,导致谁也过不了桥,这就是死锁。其实死锁形成的关键就是:谁也不让谁,谁都不会主动地让步。


二、死锁的简单例子

例子1:

//类1,d1方法为需要获得类1以及类2对象的方法  class DeadLock {  	private OtherService otherService;    	private static final Object LOCK = new Object();    	public DeadLock(OtherService otherService){  		this.otherService = otherService;  	}    	public void d1(){  		synchronized (LOCK){  			System.out.println("d1===========");  			otherService.o2();  		}  	}    	public void d2(){  		synchronized (LOCK){  			System.out.println("d2===========");  		}  	}    }    //类1,o1方法为需要获得类1以及类2对象的方法  class OtherService {  	private DeadLock deadLock;    	private static final Object LOCK = new Object();    	public void o1() {  		synchronized (LOCK){  			System.out.println("o1===========");  			deadLock.d2();  		}  	}    	public void o2() {  		synchronized (LOCK){  			System.out.println("o2===========");  		}  	}    	public void setDeadLock(DeadLock deadLock) {  		this.deadLock = deadLock;  	}  }      public class DeadLockTest {  	public static void main(String[] args) {  		OtherService otherService = new OtherService();  		DeadLock deadLock = new DeadLock(otherService);  		otherService.setDeadLock(deadLock);    		/**  		 * cmd通过jps命令查看该main线程的端口号  		 * 再通过jstack + 上面查到的端口号的命令查看栈情况  		 * 可以分析出来死锁情况:是否存在死锁  		 */  		new Thread(()->{  			while (true){  				deadLock.d1();  			}  		}).start();  		new Thread(()->{  			while (true){  				otherService.o1();  			}  		}).start();    	}    }

 上述代码描述了Java中死锁最简单的情况,一个线程Thread-0持有锁L0并且申请获得锁L1,而另一个线程Thread-1持有锁L1并且申请获得锁L0,因为默认的锁申请操作都是阻塞的,所以线程Thrad-0和Thread-1永远被阻塞了。导致了死锁。  虽然这种僵持情况由于线程程序运行时间可能错开,而不在程序运行的开始马上发生,但是这种结构的程序,若无其他代码进行死锁去除保障,那么死锁现象一定会发生。

例子2:

public class DeadLock_join {      public static void main(String[] args) {          System.out.println("main线程开始运行");            try {              Thread.currentThread().join();          } catch (InterruptedException e) {              e.printStackTrace();          }            System.out.println("如果打印出这段话则证明没有死锁");      }  }

我们可以查看控制台观察到语句:如果打印出这段话则证明没有死锁永远得不到执行。这也是死锁的一个典型例子: 使用自己的线程对象作为同步锁,调用join方法,那么会造成死锁。


三、死锁的CMD查阅操作演示:

  1. cmd通过jps命令查看该main线程的端口号 Win+R,输入cmd,即可保证打开CMD端口。输入jps,根据main方法所在类的名字,找到线程的端口号。
  1. 再通过jstack + 上面查到的端口号的命令查看栈情况

1)可以看到线程Thread-0以及Thread-1线程发生了阻塞情况,即图中最后一行的Found 1 deadlock提示所示。 2) -locked则表示线程目前锁占据的锁ID,而waiting to lock后加ID代表被当前线程锁等待的,其他线程占据的未释的锁。

 我们发现线程Thread-0锁占据的锁,正是Thread-1锁等待的,而Thread-0等待的锁恰好是Thread-1锁占据的锁,除非线程等待的锁被其他线程释放了,那么当前线程才会释放自己的锁,那么其他线程才有机会获得当前线程的锁。恰恰因为线程的不主动让步,形成了死锁,2个线程”卡住了“。

注意事项: 很多人可能在CMD上输入jps/jstack等命令却不被系统识别,一般都是JDK环境在Win系统安装不正确导致,建议重新j检查环境变量是否安装正确。