java中为了保证共享数据的安全性,咱们引入了锁的机制。有了锁就有可能产生死锁。java
死锁的缘由就是多个线程锁住了对方所须要的资源,而后现有的资源又没有释放,从而致使循环等待的状况。git
一般来讲若是不一样的线程对加锁和释放锁的顺序不一致的话,就颇有可能产生死锁。github
咱们来看一个不一样加锁顺序的例子:安全
public class DiffLockOrder { private int amount; public DiffLockOrder(int amount){ this.amount=amount; } public void transfer(DiffLockOrder target,int transferAmount){ synchronized (this){ synchronized (target){ if(amount< transferAmount){ System.out.println("余额不足!"); }else{ amount=amount-transferAmount; target.amount=target.amount+transferAmount; } } } } }
上面的例子中,咱们模拟一个转帐的过程,amount用来表示用户余额。transfer用来将当前帐号的一部分金额转移到目标对象中。dom
为了保证在transfer的过程当中,两个帐户不被别人修改,咱们使用了两个synchronized关键字,分别把transfer对象和目标对象进行锁定。this
看起来好像没问题,可是咱们没有考虑在调用的过程当中,transfer的顺序是能够发送变化的:线程
DiffLockOrder account1 = new DiffLockOrder(1000); DiffLockOrder account2 = new DiffLockOrder(500); Runnable target1= ()->account1.transfer(account2,200); Runnable target2= ()->account2.transfer(account1,100); new Thread(target1).start(); new Thread(target2).start();
上面的例子中,咱们定义了两个account,而后两个帐户互相转帐,最后颇有可能致使互相锁定,最后产生死锁。code
使用两个sync会有顺序的问题,那么有没有办法只是用一个sync就能够在全部的实例中同步呢?对象
有的,咱们可使用private的类变量,由于类变量是在全部实例中共享的,这样一次sync就够了:排序
public class LockWithPrivateStatic { private int amount; private static final Object lock = new Object(); public LockWithPrivateStatic(int amount){ this.amount=amount; } public void transfer(LockWithPrivateStatic target, int transferAmount){ synchronized (lock) { if (amount < transferAmount) { System.out.println("余额不足!"); } else { amount = amount - transferAmount; target.amount = target.amount + transferAmount; } } } }
咱们产生死锁的缘由是没法控制上锁的顺序,若是咱们可以控制上锁的顺序,是否是就不会产生死锁了呢?
带着这个思路,咱们给对象再加上一个id字段:
private final long id; // 惟一ID,用来排序 private static final AtomicLong nextID = new AtomicLong(0); // 用来生成ID public DiffLockWithOrder(int amount){ this.amount=amount; this.id = nextID.getAndIncrement(); }
在初始化对象的时候,咱们使用static的AtomicLong类来为每一个对象生成惟一的ID。
在作transfer的时候,咱们先比较两个对象的ID大小,而后根据ID进行排序,最后安装顺序进行加锁。这样就可以保证顺序,从而避免死锁。
public void transfer(DiffLockWithOrder target, int transferAmount){ DiffLockWithOrder fist, second; if (compareTo(target) < 0) { fist = this; second = target; } else { fist = target; second = this; } synchronized (fist){ synchronized (second){ if(amount< transferAmount){ System.out.println("余额不足!"); }else{ amount=amount-transferAmount; target.amount=target.amount+transferAmount; } } } }
死锁是互相请求对方占用的锁,可是对方的锁一直没有释放,咱们考虑一下,若是获取不到锁的时候,自动释放已占用的锁是否是也能够解决死锁的问题呢?
由于ReentrantLock有一个tryLock()方法,咱们可使用这个方法来判断是否可以获取到锁,获取不到就释放已占有的锁。
咱们使用ReentrantLock来完成这个例子:
public class DiffLockWithReentrantLock { private int amount; private final Lock lock = new ReentrantLock(); public DiffLockWithReentrantLock(int amount){ this.amount=amount; } private void transfer(DiffLockWithReentrantLock target, int transferAmount) throws InterruptedException { while (true) { if (this.lock.tryLock()) { try { if (target.lock.tryLock()) { try { if(amount< transferAmount){ System.out.println("余额不足!"); }else{ amount=amount-transferAmount; target.amount=target.amount+transferAmount; } break; } finally { target.lock.unlock(); } } } finally { this.lock.unlock(); } } //随机sleep必定的时间,保证能够释放掉锁 Thread.sleep(1000+new Random(1000L).nextInt(1000)); } } }
咱们把两个tryLock方法在while循环中,若是不能获取到锁就循环遍历。
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-dead-lock/
最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注个人公众号:「程序那些事」,懂技术,更懂你!