JAVA中的活锁
什么是活锁
两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
百度定义:活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
造成死锁的原因
当一系列封锁不能按照其先后顺序执行时,就可能导致一些事务无限期等待某个封锁,从而导致活锁。
活锁的解决
每个线程休眠随机数,错开拿锁的时间。
活锁重现
还拿我们死锁中转账的业务,也可以使用显示锁来解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
public class TransferMoneyDeadlock {
public static void transfer(Account from, Account to, int amount) { while (true) { if (from.tryLock()) { System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + from.name + "】账户锁成功"); sleep(100); try { System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + to.name + "】账户锁成功"); if (from.balance < amount) { System.out.println("余额不足"); return; } else { if (to.tryLock()) { sleep(100); try { from.debit(amount); to.credit(amount); System.out.println("线程【" + Thread.currentThread().getId() + "】从【" + from.name + "】账户转账到【" + to.name + "】账户【" + amount + "】元钱成功"); return; } finally { System.out.println("线程【" + Thread.currentThread().getId() + "】释放TO锁【" + to.name + "】成功"); to.unLock(); } } } } finally { System.out.println("线程【" + Thread.currentThread().getId() + "】释放FROM锁【" + from.name + "】锁成功"); from.unLock(); } } }
}
private static class Account { private Lock lock = new ReentrantLock();
String name; int balance;
public Account(String name, int balance) { this.name = name; this.balance = balance; }
void debit(int amount) { this.balance = balance - amount; }
void credit(int amount) { this.balance = balance + amount; }
boolean tryLock() { return lock.tryLock(); }
void unLock() { lock.unlock(); } }
private static void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); Account A = new Account("A", 100); Account B = new Account("B", 200); executorService.execute(() -> transfer(A, B, 5)); executorService.execute(() -> transfer(B, A, 10)); executorService.shutdown(); } }
|
输出
1 2 3 4 5 6 7 8 9
| 线程【13】获取【A】账户锁成功 线程【13】释放FROM锁【B】锁成功 线程【13】获取【B】账户锁成功 线程【12】获取【B】账户锁成功 线程【12】释放FROM锁【A】锁成功 线程【12】获取【A】账户锁成功 线程【13】获取【A】账户锁成功 线程【13】释放FROM锁【B】锁成功 ....
|
我们发现 转账没有成功一直在尝试拿锁释放锁,没有做具体的事情,但是也没有阻塞,这就是活锁
避开活锁很简单休眠一个随机数字,把这行代码解开即可
1 2
| sleep(new Random().nextInt(10));
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| 线程【12】获取【A】账户锁成功 线程【13】获取【B】账户锁成功 线程【13】获取【A】账户锁成功 线程【13】释放FROM锁【B】锁成功 线程【12】获取【B】账户锁成功 线程【12】从【A】账户转账到【B】账户【5】元钱成功 线程【12】释放TO锁【B】成功 线程【12】释放FROM锁【A】锁成功 线程【13】获取【B】账户锁成功 线程【13】获取【A】账户锁成功 线程【13】从【B】账户转账到【A】账户【10】元钱成功 线程【13】释放TO锁【A】成功 线程【13】释放FROM锁【B】锁成功
|
虽然还有部分尝试拿锁,因为我们休眠了100ms ,但是我们的代码是成功的。
总结
在开发中应该想办法避免死锁,可以尝试使用显示锁,但是显示锁要小心活锁的产生,一直在尝试拿锁释放锁,不做任何事情。