我们知道,当线程A由于某种原因(如等待IO操作完成、调用了sleep函数等)放弃了执行权时,操作系统就会调度另一个处于就绪状态(Runnable) 的线程B来执行。只有当线程A所等待的事件发生(如IO操作完成,睡眠时间结束)后,线程A才会被设置成就绪状态,等待操作系统的调度。
然而有时候可能会出现这样一种情况:线程A为了等待线程B而处于阻塞状态(blocked),此时线程B恰好又在等待线程A而处于阻塞状态。这时A,B都在相互等待对方,因而A,B谁都不能得到执行机会。这种由于线程的相互等待而导致这些线程都无法执行的现象,就叫做线程死锁。
下面我们手动造成线程死锁,来体会下死锁的概念。
先要说明一点的是,在Java中,synchronized关键字也可以用来声明一个方法,如public synchronized run()。一个同步的方法是通过请求this对象的监视器来实现同步的,这是理解以下程序的前提。
我们首先定义一个实现了Runnable接口的类ThreadLock,然后定义一个同步的方法fun(),并在该方法内部定义了一个请求Object对象的同步块;
在run()方法中定义一个请求Object对象的同步块,接着在后面再定义一个请求this对象的同步块;
描述起来比较抽象,还是直接上代码吧。。
package cls; public class ThreadLockDemo { /** * @param args */ public static void main(String[] args) { ThreadLock tl = new ThreadLock(); new Thread(tl).start(); new Thread(tl).start(); } } class ThreadLock implements Runnable { private Object obj = new Object(); // Synchronized Object boolean bool = false; // // Synchronized method run() public void run() { if(bool == true) { fun(); } else { synchronized (obj) { bool = true; // Sleep for a while, give up running. try { Thread.sleep(50); } catch(Exception e) { e.printStackTrace(); } // Enter the synchronized block, using this Object. synchronized(this) { System.out.println(Thread.currentThread().getName() + " is running !"); } } } } // Synchronized method fun() public synchronized void fun() { synchronized(obj) { while(true) { System.out.println(Thread.currentThread().getName() + " is running !"); } } } }
执行这个程序时,我们发现没有任何输出结果,而此时程序也并没有退出。这时候程序中的两个线程都因为等待对方而得不到执行的机会,发生了死锁。
我们来分析一下这个程序发生死锁现象的过程:
首先,线程A启动,bool的值为false,执行 run()方法中的else分支,给obj对象加锁,然后将bool改成true,再睡眠。此时A就已经放弃了执行权。
这时线程B启动,bool的值为 true,则执行run()方法中的第一个分支,调用fun()方法。因为fun()是一个同步的方法,所以它会去检查this对象有没有被加锁,结果是没有,所以程序进入到fun()方法中,并给this 对象加锁。接着又遇到了synchronized(obj)同步块,由于线程A已经给obj对象加了锁,因此线程B是无法进入到此同步块中去的,只能等待。
这时线程A的睡眠时间到,从上次中断的地方继续往下执行,于是就遇到了synchronized(this)同步块。由于刚刚线程B已经给this对象加了锁,因而线程A无法进入到该同步块中,只能等待。
此时,就形成了线程A,B的死锁现象。
在多线程程序设计中,线程同步是一个非常复杂的问题,一旦处理不好,极有可能出现这样那样的问题。我们在实际应用中一定要多加小心,尽量避免此类错误的发生。