抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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();
}
}
//休眠随机数字,避开同时同时拿锁释放锁
// 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();
}
}

输出

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 ,但是我们的代码是成功的。

总结

​ 在开发中应该想办法避免死锁,可以尝试使用显示锁,但是显示锁要小心活锁的产生,一直在尝试拿锁释放锁,不做任何事情。

评论