当前位置: 首页 > article >正文

【老白学 Java】线程死锁是怎么回事

线程死锁是怎么回事

码老白
文章来源:《Head First Java》修炼感悟。

前面学过的线程锁,可以有效地排除线程间的干扰。 线程锁固然好用,但还需谨慎使用,稍有不慎就会出现令人痛恨的「线程死锁」。本文带你了解什么是线程死锁以及如何避免。

线程死锁是咋形成的

简单地说,线程死锁就是两个线程互相等待对方持有的对象锁,谁也不肯主动释放导致程序停滞不前。

这就是多线程并发导致的「死锁」问题,它通常具备以下几个特点:

  1. 同一时间只能由一个线程占有某个资源;
  2. 当前线程请求其它资源但又不想释放自己占有的资源;
  3. 只能由线程自主放弃某个资源,系统不会干预;
  4. 多个线程相互等待释放某个资源形成循环。

下面我们演示一个线程死锁的情况:

/**
 * 文件: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 中的每个对象都有对象锁,进入该对象的同步方法时必须先取得该对象锁;


《 上一篇 线程的并发问题(二)

http://www.kler.cn/a/509196.html

相关文章:

  • MongoDB 学习指南:深入探索非关系型数据库
  • Red Hat8:搭建FTP服务器
  • 小白:react antd 搭建框架关于 RangePicker DatePicker 时间组件使用记录 2
  • Redis超详细入门教程(基础篇)
  • PHP智慧小区物业管理小程序
  • Kubernetes(k8s)和Docker Compose本质区别
  • Unity2021.3.13崩溃的一种情况
  • Oracle 表空间的使用与创建
  • [Python学习日记-78] 基于 TCP 的 socket 开发项目 —— 模拟 SSH 远程执行命令
  • mac 安装mongodb
  • 认识软件测试 - 软实力面试题
  • 【Java 数据导出到 Word实现方案】使用EasyPOI 工具包进行简易的word操作
  • 47.数据绑定的PropertyChanged C#例子 WPF例子
  • 基于Spring Cloud的电商系统设计与实现——用户与商品模块的研究(上)
  • 基于Springboot+Vue的小区物业管理系统
  • 渗透测试常用专业术语扫盲
  • 力扣-数组-283 移动零
  • Python获取系统运行时间
  • Linux:磁盘分区
  • 单线性激光扫描、多线性激光扫描?激光扫描三维重建算法环节
  • Qt应用之MDI(多文档设计)
  • 系统架构设计师-第2章-操作系统
  • 【书生大模型实战营】Git 基础知识-L0G3000
  • 1神经网络中的神经元模型
  • ElasticSearch DSL查询之复合查询
  • CTK插件框架学习-源码下载编译(01)