如何避免死锁

2019-09-24  

并发程序一旦死锁,往往我们只能重启应用。解决死锁问题最好的办法就是避免死锁。

 

死锁发生的条件

  • 互斥,共享资源只能被一个线程占用
  • 占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
  • 不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
  • 循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源

 

避免死锁的方法

对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。

对于第一个条件 "互斥" 是不能破坏的,因为加锁就是为了保证互斥。

其他三个条件,我们可以尝试

  • 一次性申请所有的资源,破坏 "占有且等待" 条件
  •  占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
  •  按序申请资源,破坏 "循环等待" 条件

 

使用管理类一次性申请所有的资源,破坏 "占有且等待" 条件示例

package constxiong.concurrency.a023;

import java.util.HashSet;
import java.util.Set;

/**
 * 测试 一次性申请所有的资源,破坏 "占有且等待" 条件示例
 * @author ConstXiong
 * @date 2019-09-24 14:04:12
 */
public class TestBreakLockAndWait {

	//单例的资源管理类
	private final static Manger manager = new Manger();
	
	//资源1
	private static Object res1 = new Object();
	
	//资源2
	private static Object res2 = new Object();
	
	public static void main(String[] args) {
		new Thread(() -> {
			boolean applySuccess = false;
			while (!applySuccess) {
				//向管理类,申请res1和res2,申请失败,重试
				applySuccess = manager.applyResources(res1, res2);
				if (applySuccess) {
					try {
						System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
						synchronized (res1) {
							System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
							//休眠 1秒
							try {
								Thread.sleep(1000);
							} catch (Exception e) {
								e.printStackTrace();
							}
							synchronized (res2) {
								System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
							}
						}
					} finally {
						manager.returnResources(res1, res2);//归还资源
					}
				} else {
					System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
					//申请失败休眠 200 毫秒后重试
					try {
						Thread.sleep(200);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
		new Thread(() -> {
			boolean applySuccess = false;
			while (!applySuccess) {
				//向管理类,申请res1和res2,申请失败,重试
				applySuccess = manager.applyResources(res1, res2);
				if (applySuccess) {
					try {
						System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
						synchronized (res2) {
							System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
							//休眠 1秒
							try {
								Thread.sleep(1000);
							} catch (Exception e) {
								e.printStackTrace();
							}
							synchronized (res1) {
								System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
							}
						}
					} finally {
						manager.returnResources(res1, res2);//归还资源
					}
				} else {
					System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
					//申请失败休眠 200 毫秒后重试
					try {
						Thread.sleep(200);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
	}
	
}

/**
 * 资源申请、归还管理类
 * @author ConstXiong
 * @date 2019-09-24 14:10:57
 */
class Manger {
	
	//资源存放集合
	private Set<Object> resources = new HashSet<Object>();
	
	/**
	 * 申请资源
	 * @param res1
	 * @param res2
	 * @return
	 */
	synchronized boolean applyResources(Object res1, Object res2) {
		if (resources.contains(res1) || resources.contains(res1)) {
			return false;
		} else {
			resources.add(res1);
			resources.add(res2);
			return true;
		}
	}
	
	/**
	 * 归还资源
	 * @param res1
	 * @param res2
	 */
	synchronized void returnResources(Object res1, Object res2) {
		resources.remove(res1);
		resources.remove(res2);
	}
	
}

 

打印结果如下,线程-1 在线程-0 释放完资源后才能成功申请 res1 和 res2 的锁

线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res1 资源的锁
线程:Thread-1 获取到 res2 资源的锁

 

 

使用 Lock 的 tryLock() 方法,获取锁失败释放所有资源,破坏 "不可抢占" 条件示例

package constxiong.concurrency.a023;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
	
	private static Random r = new Random(); 

	private static Lock lock1 = new ReentrantLock();
	
	private static Lock lock2 = new ReentrantLock();
	
	public static void main(String[] args) {
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock1.lock();
				System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程 0 尝试获取 lock2
					if (lock2.tryLock()) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
						try {
							taskComplete = true;
						} finally {
							lock2.unlock();
						}
					} else {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 失败");
					}
				} finally {
					lock1.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock2.lock();
				System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程2 尝试获取锁 lock1
					if (lock1.tryLock()) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
						try {
							taskComplete = true;
						} finally {
							lock1.unlock();
						}
					} else {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 失败");
					}
				} finally {
					lock2.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
}

 

打印结果如下

线程:Thread-0 获取锁 lock1 成功
线程:Thread-1 获取锁 lock2 成功
线程:Thread-1 获取锁 lock1 失败
线程:Thread-1 获取锁 lock2 成功
线程:Thread-0 获取锁 lock2 失败
线程:Thread-1 获取锁 lock1 成功
线程:Thread-0 获取锁 lock1 成功
线程:Thread-0 获取锁 lock2 成功

 

 

按照一定的顺序加锁,破坏 "循环等待" 条件示例

package constxiong.concurrency.a023;

/**
 * 测试 按序申请资源,破坏 "循环等待" 条件
 * @author ConstXiong
 * @date 2019-09-24 15:26:23
 */
public class TestBreakLockCircleWait {

	private static Object res1 = new Object();
	
	private static Object res2 = new Object();
	
	
	public static void main(String[] args) {
		new Thread(() -> {
			Object first = res1;
			Object second = res2;
			//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
			if (res1.hashCode() > res2.hashCode()) {
				first = res2;
				second = res1;
			}
			synchronized (first) {
				System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized(second) {
					System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
				}
			}
		}).start();
		
		new Thread(() -> {
			Object first = res1;
			Object second = res2;
			//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
			if (res1.hashCode() > res2.hashCode()) {
				first = res2;
				second = res1;
			}
			synchronized (first) {
				System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized(second) {
					System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
				}
			}
		}).start();
	}
	
}

 

打印结果如下

线程:Thread-0获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-0获取资源 java.lang.Object@7a80f45c 锁成功
线程:Thread-1获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-1获取资源 java.lang.Object@7a80f45c 锁成功

 

ConstXiong 备案号:苏ICP备16009629号-3