【老白学 Java】线程死锁是怎么回事
线程死锁是怎么回事
文章来源:《Head First Java》修炼感悟。
前面学过的线程锁,可以有效地排除线程间的干扰。 线程锁固然好用,但还需谨慎使用,稍有不慎就会出现令人痛恨的「线程死锁」。本文带你了解什么是线程死锁以及如何避免。
线程死锁是咋形成的
简单地说,线程死锁就是两个线程互相等待对方持有的对象锁,谁也不肯主动释放导致程序停滞不前。
这就是多线程并发导致的「死锁」问题,它通常具备以下几个特点:
- 同一时间只能由一个线程占有某个资源;
- 当前线程请求其它资源但又不想释放自己占有的资源;
- 只能由线程自主放弃某个资源,系统不会干预;
- 多个线程相互等待释放某个资源形成循环。
下面我们演示一个线程死锁的情况:
/**
* 文件:DeadLockDemo.java
* 描述:一个用于模拟线程死锁问题的程序代码。
*/
public class DeadLockDemo {
public static void main(String[] args) {
// 表示两个资源对象
Object obj1 = new Object();
Object obj2 = new Object();
// 创建两个线程,并传入两个资源对象
Thread t1 = new Thread(new DeadLockOne(obj1, obj2));
Thread t2 = new Thread(new DeadLockTwo(obj1, obj2));
// 启动两个线程
t1.start();
t2.start();
}
}
// 第一个线程处理类
class DeadLockOne implements Runnable {
// 两个资源的引用对象
Object obj1;
Object obj2;
public DeadLockOne(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
// 线程处理,按顺序访问资源1->资源2
public void run() {
// 持有资源1的钥匙
synchronized(obj1) {
System.out.println("thread 1 is executing...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 准备访问资源2
System.out.println("thread 1 is ready to get resource 2...");
synchronized(obj2) {
System.out.println("thread 1 gets resource 2...");
}
}
}
}
// 第二个线程处理类
class DeadLockTwo implements Runnable {
// 两个资源的引用对象
Object obj1;
Object obj2;
public DeadLockTwo(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
// 线程处理,按顺序访问资源2->资源1
public void run() {
synchronized(obj2) {
System.out.println("thread 2 is executing...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 准备访问资源1
System.out.println("thread 2 is ready to get resource 1...");
synchronized(obj1) {
System.out.println("thread 2 gets resource 1...");
}
}
}
}
编译、运行这段代码:
可以看到,程序并没有退出,但停在了那个位置不动了。 从输出也能看出,线程执行后,线程 2 持有 资源 2 准备获取资源 1,而线程 1 持有资源 1 想要获取资源 2,两个线程都在等对方先放手,结果便是无限等待。
如何预防线程死锁
为了避免线程死锁问题,最常用的方法就是对象锁进行排序,所有线程约定好获取对象钥匙的顺序。 比如上面的例子,两个线程都按照资源 1 -> 资源 2 的顺序进行获取,就不会发生线程死锁问题。 就像这样:
// 线程1处理,按顺序访问资源1->资源2
public void run() {
// 持有资源1的钥匙
synchronized(obj1) {
// do something...
synchronized(obj2) {
System.out.println("thread 1 gets resource 2...");
}
}
}
// 线程2处理,按顺序访问资源1->资源2
public void run() {
// 持有资源1的钥匙
synchronized(obj1) {
// do something...
synchronized(obj2) {
System.out.println("thread 2 gets resource 2...");
}
}
}
再来运行一次:
这次两个线程都得到了自己所需的资源。
其实还有几种方法也能解决线程死锁问题,这里不再赘述。 大家如果感兴趣可以参考其它专业书籍,说实话,老白水平有限怕误人子弟。
重点回顾
- 线程的静态方法
sleep()
可以强制线程进入阻塞状态,直到经过指定时间后才会重新进入就绪状态; - 调用
sleep()
的目的就是为了让每个线程都有执行的机会; sleep()
方法可能会抛出 InterruptedException 异常,所以应该包含在try-catch
结构中;- 使用
setName()
可以为线程设置一个名称; - 如果有两个以上的线程同时存取某个对象,可能会破坏对象数据;
- 为确保数据安全,应该考虑合并存取数据的代码;
synchronized
可以防止两个以上的线程同时进入某个对象的同一个方法;- Java 中的每个对象都有对象锁,进入该对象的同步方法时必须先取得该对象锁;
《 上一篇 线程的并发问题(二) |
---|