java中的活锁
什么是活锁
两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
百度定义:活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
造成活锁的原因
当一系列封锁不能按照其先后顺序执行时,就可能导致一些事务无限期等待某个封锁,从而导致活锁。
活锁的解决
每个线程休眠随机数,错开拿锁的时间。
活锁重现
还拿我们死锁中转账的业务,也可以使用显示锁来解决
/**
* 活锁
* 转账业务
*/
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();
}
}
//休眠随机数字,避开同时同时拿锁释放锁
// sleep(new Random().nextInt(10));
}
}
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();
}
}
/**
* 休眠
*
* @param time
*/
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);
//创建账户A
Account A = new Account("A", 100);
//创建账户B
Account B = new Account("B", 200);
//A -> B 的转账
executorService.execute(() -> transfer(A, B, 5));
//B -> A 的转账
executorService.execute(() -> transfer(B, A, 10));
executorService.shutdown();
}
}
输出
线程【13】获取【A】账户锁成功
线程【13】释放FROM锁【B】锁成功
线程【13】获取【B】账户锁成功
线程【12】获取【B】账户锁成功
线程【12】释放FROM锁【A】锁成功
线程【12】获取【A】账户锁成功
线程【13】获取【A】账户锁成功
线程【13】释放FROM锁【B】锁成功
....
现在同时出现两笔交易,账户A转账户B,账户B转账户 A。那么,线程一会占有账户A,线程二则会占有账户B。
结果,线程一拿不到账号B,转账没法执行,线程一结束;线程二也拿不到账号A,转账也没法执行,线程二结束。
到这里,第一轮循环结束,两笔转账都没有完成,新一轮循环开启。但在第二轮循环中,上面的过程又再次重复。如此不断地循环下去,两笔转账却一直没有完成。
我们发现 转账没有成功一直在尝试拿锁释放锁,没有做具体的事情,但是也没有阻塞,这就是活锁
避开活锁很简单休眠一个随机数字,把这行代码解开即可
//休眠随机数字,避开同时同时拿锁释放锁
sleep(new Random().nextInt(10));
复制线程【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 ,但是我们的代码是成功的。
总结
在开发中应该想办法避免死锁,可以尝试使用显示锁,但是显示锁要小心活锁的产生,一直在尝试拿锁释放锁,不做任何事情。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 牧马人的忧伤!
评论