面试题答案
一键面试1. 代码修改
以下以Java代码为例,假设原来的代码类似这样(简化示例,实际转账操作会更复杂且涉及数据库事务等):
class Account {
private int balance;
public Account(int initialBalance) {
this.balance = initialBalance;
}
public int getBalance() {
return balance;
}
public void transfer(Account to, int amount) {
synchronized (this) {
synchronized (to) {
if (this.balance >= amount) {
this.balance -= amount;
to.balance += amount;
}
}
}
}
}
为了让其可能出现活锁,我们可以修改如下:
class Account {
private int balance;
public Account(int initialBalance) {
this.balance = initialBalance;
}
public int getBalance() {
return balance;
}
public void transfer(Account to, int amount) {
while (true) {
if (System.currentTimeMillis() % 2 == 0) {
synchronized (this) {
synchronized (to) {
if (this.balance >= amount) {
this.balance -= amount;
to.balance += amount;
break;
}
}
}
} else {
// 尝试不同的锁获取顺序,并且每次循环都重试
synchronized (to) {
synchronized (this) {
if (this.balance >= amount) {
this.balance -= amount;
to.balance += amount;
break;
}
}
}
}
}
}
}
2. 活锁出现场景
假设有两个线程 Thread1
和 Thread2
,Thread1
负责A账户向B账户转账,Thread2
负责B账户向A账户转账。当这两个线程同时运行时,由于每次获取锁的顺序随机(这里通过 System.currentTimeMillis() % 2
模拟),可能会出现这样的情况:Thread1
先获取A账户的锁,同时 Thread2
先获取B账户的锁,然后 Thread1
尝试获取B账户的锁,Thread2
尝试获取A账户的锁,由于都获取不到对方已持有的锁,它们会不断地重试获取锁(按照不同顺序),但是始终无法成功完成转账操作,这就形成了活锁。
3. 与死锁在现象上的差异
- 死锁:线程处于一种永久阻塞状态,所有涉及的线程都在等待对方释放资源,没有任何线程可以继续执行,程序完全停滞。例如在上述转账场景中,两个线程分别持有自己账户的锁,等待对方账户的锁,且不会主动放弃已持有的锁,程序看起来像是“卡死”了。
- 活锁:线程并没有被阻塞,它们都在持续尝试执行操作,但是由于相互干扰,始终无法取得进展。就像两个人在狭窄的走廊相遇,都想给对方让路,但是每次都同时往同一个方向移动,结果一直在原地“折腾”,程序一直在运行,但没有实际的业务进展。