赞
踩
死锁是指两个或两个以上的进程在执行过程中,因争抢资源而造成的一种互相等待的现象,若无外力干涉它们将无法推进,如果系统资源充足,进程的资源请求能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
死锁产生的原因:【1】系统资源不足;【2】资源分配不当;【3】进程运行推进的顺序不合适;
形成死锁的四个必要条件:
**【1】互斥条件:**一个资源每次只能被一个进程使用。
**【2】请求与保持条件:**一个进程因请求资源而阻塞时,对已获得的资源保持不放。
**【3】不剥夺条件:**进程已获得的资源,在末使用完之前,不能强行剥夺。
**【4】循环等待条件:**若干进程之间形成一种头尾相接的循环等待资源关系。
1 public class TestMian { 2 //A、B 表示两把锁 3 String A = "A"; 4 String B = "B"; 5 public static void main(String[] args) { 6 TestMian testMian = new TestMian(); 7 new Thread(()->{ 8 try { 9 testMian.a(); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 }).start(); 14 15 new Thread(()->{ 16 try { 17 testMian.b(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 }).start(); 22 } 23 24 25 public void a() throws InterruptedException { 26 //持有锁A后,尝试持有锁B ***********重点************** 27 synchronized (A){ 28 System.out.println("A"); 29 TimeUnit.SECONDS.sleep(1); 30 synchronized (B){ 31 System.out.println("B"); 32 } 33 } 34 } 35 36 public void b() throws InterruptedException { 37 //持有锁B后,尝试持有锁A ***********重点************** 38 synchronized (B){ 39 System.out.println("B"); 40 TimeUnit.SECONDS.sleep(1); 41 synchronized (A){ 42 System.out.println("A"); 43 } 44 } 45 } 46 }
【1】**jps 命令定位进程号:**window 下 java 运行程序,也有类似与 Linux 操作系统的 ps -ef|grep xxx 的查看进程的命令,我们这里只查看 java 的进程,即使用 jps 命令
【2】jstack 能够找到死锁信息:
**【1】破坏互斥条件:**这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
**【2】破坏请求与保持条件:**一次性申请所有的资源。
**【3】破坏不剥夺条件:**占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
**【4】破坏循环等待条件:**靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
避免死锁可以概括成三种方法:
【1】固定加锁的顺序(针对锁顺序死锁);
【2】开放调用(针对对象之间协作造成的死锁);
【3】使用定时锁 tryLock();使用显式 Lock锁,在获取锁时使用 tryLock()
方法。当等待超过时限的时候,**tryLock()
不会一直等待,而是返回错误信息。使用tryLock()
**能够有效避免死锁问题。
如果等待获取锁时间超时,则抛出异常而不是一直等待!
【1】不同的加锁顺序案例:
1 public class DiffLockOrder { 2 3 private int amount; 4 5 public DiffLockOrder(int amount){ 6 this.amount=amount; 7 } 8 9 // 第一个锁 this 是 DiffLockOrder的对象,第二个锁target 也是 DiffLockOrder对象,为死锁埋下了隐患 10 public void transfer(DiffLockOrder target,int transferAmount){ 11 synchronized (this){ 12 synchronized (target){ 13 if(amount< transferAmount){ 14 System.out.println("余额不足!"); 15 }else{ 16 amount=amount-transferAmount; 17 target.amount=target.amount+transferAmount; 18 } 19 } 20 } 21 } 22 }
【2】 上面的例子中,我们模拟一个转账的过程,amount用来表示用户余额。transfer用来将当前账号的一部分金额转移到目标对象中。为了保证在 transfer的过程中,两个账户不被别人修改,我们使用了两个synchronized 关键字,分别把 transfer对象和目标对象进行锁定。看起来好像没问题,但是我们没有考虑在调用的过程中,transfer的顺序是可以发送变化的:
1 DiffLockOrder account1 = new DiffLockOrder(1000);
2 DiffLockOrder account2 = new DiffLockOrder(500);
3
4 Runnable target1= ()->account1.transfer(account2,200);
5 Runnable target2= ()->account2.transfer(account1,100);
6 new Thread(target1).start();
7 new Thread(target2).start();
上面的例子中,我们定义了两个account,然后两个账户互相转账,最后很有可能导致互相锁定,最后产生死锁。
**解决方案一:**使用 private类变量,只是用一个 sync就可以在所有的实例中同步,来解决两个 sync顺序问题。因为类变量是在所有实例中共享的,这样一次sync就够了:
1 public class LockWithPrivateStatic { 2 3 private int amount; 4 // 不管有多少个实例,共享同一个 lock 5 private static final Object lock = new Object(); 6 7 public LockWithPrivateStatic(int amount){ 8 this.amount=amount; 9 } 10 11 public void transfer(LockWithPrivateStatic target, int transferAmount){ 12 synchronized (lock) { 13 if (amount < transferAmount) { 14 System.out.println("余额不足!"); 15 } else { 16 amount = amount - transferAmount; 17 target.amount = target.amount + transferAmount; 18 } 19 } 20 } 21 }
**解决方案二:**使用相同的Order,我们产生死锁的原因是无法控制上锁的顺序,如果我们能够控制上锁的顺序,是不是就不会产生死锁了呢?带着这个思路,我们给对象再加上一个 id字段:
1 private final long id; // 唯一ID,用来排序
2 private static final AtomicLong nextID = new AtomicLong(0); // 用来生成ID
3
4 public DiffLockWithOrder(int amount){
5 this.amount=amount;
6 this.id = nextID.getAndIncrement();
7 }
在初始化对象的时候,我们使用 static的 AtomicLong类来为每个对象生成唯一的ID。在做 transfer的时候,我们先比较两个对象的ID大小,然后根据 ID进行排序,最后安装顺序进行加锁。这样就能够保证顺序,从而避免死锁。
1 public void transfer(DiffLockWithOrder target, int transferAmount){ 2 //将加锁的对象修改为可变参数,ID小的永远为第一个锁对象 3 DiffLockWithOrder fist, second; 4 5 if (compareTo(target) < 0) { 6 fist = this; 7 second = target; 8 } else { 9 fist = target; 10 second = this; 11 } 12 13 synchronized (fist){ 14 synchronized (second){ 15 if(amount< transferAmount){ 16 System.out.println("余额不足!"); 17 }else{ 18 amount=amount-transferAmount; 19 target.amount=target.amount+transferAmount; 20 } 21 } 22 } 23 }
**解决方案三:**释放掉已占有的锁,死锁是互相请求对方占用的锁,但是对方的锁一直没有释放,我们考虑一下,如果获取不到锁的时候,自动释放已占用的锁是不是也可以解决死锁的问题呢?因为 ReentrantLock有一个 tryLock()方法,我们可以使用这个方法来判断是否能够获取到锁,获取不到就释放已占有的锁。我们使用 ReentrantLock来完成这个例子:
1 public class DiffLockWithReentrantLock { 2 3 private int amount; 4 private final Lock lock = new ReentrantLock(); 5 6 public DiffLockWithReentrantLock(int amount){ 7 this.amount=amount; 8 } 9 10 private void transfer(DiffLockWithReentrantLock target, int transferAmount) 11 throws InterruptedException { 12 while (true) { 13 if (this.lock.tryLock()) { 14 try { 15 if (target.lock.tryLock()) { 16 try { 17 if(amount< transferAmount){ 18 System.out.println("余额不足!"); 19 }else{ 20 amount=amount-transferAmount; 21 target.amount=target.amount+transferAmount; 22 } 23 break; 24 } finally { 25 target.lock.unlock(); 26 } 27 } 28 } finally { 29 this.lock.unlock(); 30 } 31 } 32 //随机sleep一定的时间,保证可以释放掉锁 33 Thread.sleep(1000+new Random(1000L).nextInt(1000)); 34 } 35 } 36 37 }
我们把两个 tryLock方法在 while循环中,如果不能获取到锁就循环遍历。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。