在多线程编程中,除了要解决数据访问的同步与互斥之外,还需要解决的重要问题就是多线程的死锁问题。所谓死锁: 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外部处理作用,它们都将无限等待下去。
一、死锁原因与形成条件
死锁形成的原因:
- 系统资源不足
- 进程(线程)推进的顺序不恰当;
- 资源分配不当
死锁形成的条件:
- 互斥条件:所谓互斥就是进程在某一时间内独占资源。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
从编程经验上来讲,形成死锁的一般原因有以下几种:
- 个人使用锁的经验差异。
- 程序模块使用锁的差异。
- 工程代码版本之间的差异。
- 工程代码分支之间的差异。
- 修改代码和重构代码带来的差异。
二、常见死锁形成的场景
死锁形成的常见情况有以下几种:
2.1 忘记释放锁
void data_process()
{
EnterCriticalSection();
if(/* error happens, forget LeaveCriticalSection */)
return;
LeaveCriticalSection();
}
2.2 单线程重复申请锁
void sub_func()
{
EnterCriticalSection();
do_something();
LeaveCriticalSection();
}
void data_process()
{
EnterCriticalSection();
sub_func();
LeaveCriticalSection();
}
2.3 多线程多锁申请
void data_process1()
{
EnterCriticalSection(&cs1); // 申请锁的顺序有依赖
EnterCriticalSection(&cs2);
do_something1();
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
void data_process2()
{
EnterCriticalSection(&cs2); // 申请锁的顺序有依赖
EnterCriticalSection(&cs1);
do_something2();
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
}
2.4 环形锁申请
/* 多个线程申请锁的顺序形成相互依赖的环形: * A - B * | | * C - D */
三、死锁的避免策略
死锁的代价是非常大的,有时候很难检测排查,因此需要在编程过程中尽可能的避免发生死锁。编程中为了避免死锁应该遵循如下策略:
- 在编写多线程程序之前,首先编写正确的程序,然后再移植到多线程。
- 时刻检查自己写的程序有没有在跳出时忘记释放锁。
- 如果自己的模块可能重复使用一个锁,建议使用嵌套锁。
- 对于某些锁代码,不要临时重新编写,建议使用库里面的锁,或者自己曾经编写的锁。
- 如果某项业务需要获取多个锁,必须保证锁的按某种顺序获取,否则必定死锁。
- 编写简单的测试用例,验证有没有死锁。
- 编写验证死锁的程序,从源头避免死锁。
四、死锁例子
张三对李四说:“你给我画,我就把书给你。
李四对张三说:“你给我书,我就把画给你
class Zhangsan{ // 定义张三类
public void say(){
System.out.println("张三对李四说:“你给我画,我就把书给你。”") ;
}
public void get(){
System.out.println("张三得到画了。") ;
}
};
class Lisi{ // 定义李四类
public void say(){
System.out.println("李四对张三说:“你给我书,我就把画给你”") ;
}
public void get(){
System.out.println("李四得到书了。") ;
}
};
public class ThreadDeadLock implements Runnable{
private static Zhangsan zs = new Zhangsan() ; // 实例化static型对象
private static Lisi ls = new Lisi() ; // 实例化static型对象
private boolean flag = false ; // 声明标志位,判断那个先说话
public void run(){ // 覆写run()方法
if(flag){
synchronized(zs){ // 同步张三
zs.say() ;
try{
Thread.sleep(500) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
synchronized(ls){
zs.get() ;
}
}
}else{
synchronized(ls){
ls.say() ;
try{
Thread.sleep(500) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
synchronized(zs){
ls.get() ;
}
}
}
}
public static void main(String args[]){
ThreadDeadLock t1 = new ThreadDeadLock() ; // 控制张三
ThreadDeadLock t2 = new ThreadDeadLock() ; // 控制李四
t1.flag = true ;
t2.flag = false ;
Thread thA = new Thread(t1) ;
Thread thB = new Thread(t2) ;
thA.start() ;
thB.start() ;
}
};