MST

星途 面试题库

面试题:并发与同步:活锁概念及与死锁区别的基础应用

在一个多线程的银行转账模拟程序中,A账户向B账户转账,B账户向A账户转账,可能出现死锁。请修改代码,让它可能出现活锁,并简要说明活锁出现的场景以及与死锁相比在现象上的差异。
43.4万 热度难度
操作系统并发与同步

知识考点

AI 面试

面试题答案

一键面试

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. 活锁出现场景

假设有两个线程 Thread1Thread2Thread1 负责A账户向B账户转账,Thread2 负责B账户向A账户转账。当这两个线程同时运行时,由于每次获取锁的顺序随机(这里通过 System.currentTimeMillis() % 2 模拟),可能会出现这样的情况:Thread1 先获取A账户的锁,同时 Thread2 先获取B账户的锁,然后 Thread1 尝试获取B账户的锁,Thread2 尝试获取A账户的锁,由于都获取不到对方已持有的锁,它们会不断地重试获取锁(按照不同顺序),但是始终无法成功完成转账操作,这就形成了活锁。

3. 与死锁在现象上的差异

  • 死锁:线程处于一种永久阻塞状态,所有涉及的线程都在等待对方释放资源,没有任何线程可以继续执行,程序完全停滞。例如在上述转账场景中,两个线程分别持有自己账户的锁,等待对方账户的锁,且不会主动放弃已持有的锁,程序看起来像是“卡死”了。
  • 活锁:线程并没有被阻塞,它们都在持续尝试执行操作,但是由于相互干扰,始终无法取得进展。就像两个人在狭窄的走廊相遇,都想给对方让路,但是每次都同时往同一个方向移动,结果一直在原地“折腾”,程序一直在运行,但没有实际的业务进展。