活锁检测与避免:比死锁更隐蔽🔄

74 阅读3分钟

死锁是抱着不放,活锁是互相谦让到天荒地老!两个线程都在运行,但都无法继续执行。

一、什么是活锁?

死锁 vs 活锁

死锁: 线程阻塞,互相等待

// 线程A持有锁1,等待锁2
// 线程B持有锁2,等待锁1
// 两个线程都BLOCKED

活锁: 线程运行,但无进展

// 线程A释放资源,等待B
// 线程B释放资源,等待A
// 两个线程都RUNNABLE,但互相谦让

生活类比

死锁像两辆车在窄路相遇🚗🚙:

  • 谁都不让,卡死

活锁像两个人在走廊相遇🚶🚶:

  • 都想让路
  • 同时左移 → 同时右移 → 永远相遇
  • 一直在动,但都过不去

二、活锁示例代码

示例1:资源谦让

public class LiveLockDemo {
    
    static class Spoon {
        private Diner owner;
        
        public Spoon(Diner owner) {
            this.owner = owner;
        }
        
        public synchronized void use() {
            System.out.println(owner.name + " 用勺子吃饭");
        }
        
        public synchronized void setOwner(Diner d) {
            owner = d;
        }
        
        public synchronized Diner getOwner() {
            return owner;
        }
    }
    
    static class Diner {
        private String name;
        private boolean isHungry;
        
        public Diner(String name) {
            this.name = name;
            this.isHungry = true;
        }
        
        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                // 如果勺子不是自己的
                if (spoon.getOwner() != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        continue;
                    }
                    continue;
                }
                
                // 如果配偶也饿了,把勺子让给对方
                if (spouse.isHungry) {
                    System.out.println(name + ": 你先吃吧亲爱的");
                    spoon.setOwner(spouse);  // 谦让
                    continue;  // 继续循环
                }
                
                // 自己吃
                spoon.use();
                isHungry = false;
                spoon.setOwner(spouse);
            }
        }
    }
    
    public static void main(String[] args) {
        Diner husband = new Diner("丈夫");
        Diner wife = new Diner("妻子");
        
        Spoon spoon = new Spoon(husband);
        
        new Thread(() -> husband.eatWith(spoon, wife)).start();
        new Thread(() -> wife.eatWith(spoon, husband)).start();
        
        // 输出:
        // 丈夫: 你先吃吧亲爱的
        // 妻子: 你先吃吧亲爱的
        // 丈夫: 你先吃吧亲爱的
        // ... 永远循环,活锁!
    }
}

三、活锁的三种解决方案

方案1:随机退避⭐推荐

public void eatWith(Spoon spoon, Diner spouse) {
    while (isHungry) {
        if (spoon.getOwner() != this) {
            Thread.sleep(1);
            continue;
        }
        
        if (spouse.isHungry) {
            System.out.println(name + ": 你先吃吧");
            spoon.setOwner(spouse);
            
            // ✅ 随机等待一段时间
            int waitTime = ThreadLocalRandom.current().nextInt(1, 10);
            Thread.sleep(waitTime);
            
            continue;
        }
        
        spoon.use();
        isHungry = false;
    }
}

原理: 随机等待打破对称性

方案2:优先级调整

static class Diner {
    private int priority;  // 优先级
    
    public void eatWith(Spoon spoon, Diner spouse) {
        while (isHungry) {
            if (spoon.getOwner() != this) {
                Thread.sleep(1);
                continue;
            }
            
            // ✅ 优先级低的让给优先级高的
            if (spouse.isHungry && spouse.priority > this.priority) {
                System.out.println(name + ": 你优先级高,你先吃");
                spoon.setOwner(spouse);
                continue;
            }
            
            spoon.use();
            isHungry = false;
        }
    }
}

方案3:资源排序

public class ResourceOrdering {
    
    public void transfer(Account from, Account to, int amount) {
        // ✅ 按账号ID排序,避免循环等待
        Account first = from.id < to.id ? from : to;
        Account second = from.id < to.id ? to : from;
        
        synchronized (first) {
            synchronized (second) {
                from.balance -= amount;
                to.balance += amount;
            }
        }
    }
}

四、活锁检测

方法1:监控线程状态

public class LiveLockDetector {
    
    private final Map<Long, ThreadInfo> threadInfos = new ConcurrentHashMap<>();
    
    public void detect() {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        
        for (ThreadInfo info : bean.dumpAllThreads(false, false)) {
            if (info.getThreadState() == Thread.State.RUNNABLE) {
                
                // 检查栈帧是否一直重复
                ThreadInfo oldInfo = threadInfos.get(info.getThreadId());
                if (oldInfo != null) {
                    if (isSameStackTrace(oldInfo, info)) {
                        System.out.println("可能的活锁: " + info.getThreadName());
                    }
                }
                
                threadInfos.put(info.getThreadId(), info);
            }
        }
    }
    
    private boolean isSameStackTrace(ThreadInfo old, ThreadInfo current) {
        StackTraceElement[] oldStack = old.getStackTrace();
        StackTraceElement[] currentStack = current.getStackTrace();
        
        if (oldStack.length != currentStack.length) {
            return false;
        }
        
        for (int i = 0; i < oldStack.length; i++) {
            if (!oldStack[i].equals(currentStack[i])) {
                return false;
            }
        }
        
        return true;
    }
}

方法2:超时检测

public class TimeoutDetector {
    
    public boolean tryWithTimeout(Runnable task, long timeout, TimeUnit unit) {
        Future<?> future = executor.submit(task);
        
        try {
            future.get(timeout, unit);
            return true;
        } catch (TimeoutException e) {
            System.out.println("任务超时,可能活锁");
            future.cancel(true);
            return false;
        }
    }
}

五、实战:转账防活锁

public class BankAccount {
    private final long id;
    private int balance;
    
    public void transfer(BankAccount target, int amount) {
        // 方案1:资源排序
        BankAccount first = this.id < target.id ? this : target;
        BankAccount second = this.id < target.id ? target : this;
        
        synchronized (first) {
            synchronized (second) {
                if (this.balance >= amount) {
                    this.balance -= amount;
                    target.balance += amount;
                }
            }
        }
    }
    
    // 方案2:超时机制
    public boolean transferWithTimeout(BankAccount target, int amount, long timeout) {
        long deadline = System.currentTimeMillis() + timeout;
        
        while (System.currentTimeMillis() < deadline) {
            if (tryTransfer(target, amount)) {
                return true;
            }
            
            // 随机退避
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10));
            } catch (InterruptedException e) {
                return false;
            }
        }
        
        return false;
    }
    
    private boolean tryTransfer(BankAccount target, int amount) {
        if (Thread.holdsLock(this) || Thread.holdsLock(target)) {
            return false;
        }
        
        synchronized (this) {
            if (synchronized (target)) {
                if (this.balance >= amount) {
                    this.balance -= amount;
                    target.balance += amount;
                    return true;
                }
            }
        }
        
        return false;
    }
}

六、预防活锁checklist✅

□ 引入随机性(随机退避)
□ 设置优先级(避免对称)
□ 资源排序(统一顺序)
□ 超时机制(避免永久等待)
□ 监控线程状态(检测异常)
□ 避免无限重试(设置重试上限)

七、面试高频问答💯

Q: 死锁和活锁的区别?

A:

  • 死锁:线程BLOCKED,互相等待
  • 活锁:线程RUNNABLE,互相谦让
  • 饥饿:线程RUNNABLE,得不到资源

Q: 如何避免活锁?

A:

  1. 随机退避:打破对称性
  2. 优先级:避免无休止谦让
  3. 资源排序:统一获取顺序
  4. 超时:避免永久等待

Q: 如何检测活锁?

A:

  • 监控线程状态(一直RUNNABLE但无进展)
  • 检查栈帧重复
  • 设置超时告警

下一篇→ 并发三性:可见性、原子性、有序性🔺