三分钟掌握活锁(Livelock)

大家对死锁都比较熟悉,今天来快速学习一下活锁。

一、活锁的定义与原理

活锁(Livelock) 是并发编程中的问题:线程虽然没有被阻塞(仍在运行),但无法继续执行后续任务,因为彼此不断响应对方的动作,导致系统陷入“无限循环”的无效操作中。

sequenceDiagramparticipant T1 as Thread1participant T2 as Thread2T1->>T1: 获取Lock1T2->>T2: 获取Lock2T1->>T2: 尝试获取Lock2(失败)T2->>T1: 尝试获取Lock1(失败)T1->>T1: 释放Lock1T2->>T2: 释放Lock2T1->>T1: 重新获取Lock1T2->>T2: 重新获取Lock2loop 活锁循环T1->>T2: 尝试获取Lock2(失败)T2->>T1: 尝试获取Lock1(失败)T1->>T1: 释放Lock1T2->>T2: 释放Lock2end

经典场景

  • 两人在走廊相遇,反复避让导致路径持续阻塞。
  • 线程A和线程B互相释放资源并重试,导致循环冲突。

二、活锁与死锁的区别

  • 死锁:线程互相等待资源,完全停止执行。
  • 活锁:线程持续执行,但无法推进任务。

1. 死锁(Deadlock)示意图

graph TDsubgraph "死锁:线程互相等待资源"T1[线程1] -->|持有资源A,请求资源B| R1[资源A]T2[线程2] -->|持有资源B,请求资源A| R2[资源B]T1 -.->|等待资源B| T2T2 -.->|等待资源A| T1end

关键特征

  • 线程互相持有对方需要的资源,且不释放。
  • 所有线程被永久阻塞(不再运行)。
  • 形成环形等待链(如 T1 → T2 → T1)。

2. 活锁(Livelock)示意图

graph TDsubgraph "活锁:线程反复释放资源"T1 -->|尝试获取资源B| R2[资源B]T2[线程2] -->|释放资源B| R2[资源B]T2 -->|尝试获取资源A| R1[资源A]T1[线程1] -->|释放资源A| R1[资源A]T1 -.->|失败后重试| T2T2 -.->|失败后重试| T1end

关键特征

  • 线程主动释放资源并重试,但重试策略同步
  • 线程仍在运行,但任务无进展。
  • 形成无限循环的无效操作

对比总结(表格形式)

特征 死锁(Deadlock) 活锁(Livelock)
线程状态 完全阻塞(停止运行) 仍在运行(非阻塞)
资源持有 资源被永久占用 资源被反复释放和重试获取
系统表现 无 CPU 占用,任务完全停滞 高 CPU 占用,任务无进展
解决方式 强制终止线程或打破等待链 引入随机退避或优先级调度
类比场景 两人互不让路,僵持原地 两人反复避让,始终无法通过

通过对比可以看出:

  • 死锁是静态的僵局(资源被永久占用)。
  • 活锁是动态的僵局(资源被反复释放和重试)。

三、活锁的Java代码示例

以下示例模拟两个线程因资源竞争导致的活锁。示例中,两个线程反复获取和释放锁,但无法同时获得两个锁。


import java.util.concurrent.locks.ReentrantLock;public class LivelockExample {private final ReentrantLock lock1 = new ReentrantLock();private final ReentrantLock lock2 = new ReentrantLock();public void execute() {new Thread(this::process1).start();new Thread(this::process2).start();}private void process1() {while (true) {lock1.lock();System.out.println("⭐Process1 acquired lock1");try {// 关键点:直接尝试获取lock2(无延迟)if (lock2.tryLock()) {System.out.println("⭐Process1 acquired lock2. Working now.");break;} else {System.out.println("⭐Process1 failed to acquire lock2. Retrying...");}} finally {if (lock2.isHeldByCurrentThread()) lock2.unlock();lock1.unlock();}}}private void process2() {while (true) {lock2.lock();System.out.println("👽Process2 acquired lock2");try {// 对称操作:直接尝试获取lock1(无延迟)if (lock1.tryLock()) {System.out.println("👽Process2 acquired lock1. Working now.");break;} else {System.out.println("👽Process2 failed to acquire lock1. Retrying...");}} finally {if (lock1.isHeldByCurrentThread()) lock1.unlock();lock2.unlock();}}}public static void main(String[] args) {new LivelockExample().execute();}
}

示例代码行为分析

步骤 Process1 动作 Process2 动作 结果
1 获取 lock1 获取 lock2 各自持有第一个锁
2 尝试获取 lock2(失败) 尝试获取 lock1(失败) 释放已持有的锁并循环重试
3 重新获取 lock1 重新获取 lock2 重复步骤1-2,形成活锁循环

输出示例(持续循环):

⭐Process1 acquired lock1
👽Process2 acquired lock2
⭐Process1 failed to acquire lock2. Retrying...
👽Process2 failed to acquire lock1. Retrying...
👽Process2 acquired lock2
👽Process2 failed to acquire lock1. Retrying...
👽Process2 acquired lock2
👽Process2 failed to acquire lock1. Retrying...
👽Process2 acquired lock2
👽Process2 failed to acquire lock1. Retrying...
👽Process2 acquired lock2
👽Process2 failed to acquire lock1. Retrying...
...(无限循环)

四、活锁的检测与解决

检测方法

  1. 日志分析:观察日志中线程频繁重试但无进展。
  2. 性能监控:CPU占用高但任务无完成。

解决方案

  1. 随机退避:在重试时引入随机等待(如代码中获取资源时使用 sleep)。
  2. 优先级调度:为线程设置不同的重试优先级。
  3. 超时机制:限制最大重试次数后终止或回退。

五、总结

  • 活锁本质:线程因过度“礼貌”导致无效循环。
  • 避免关键:通过随机退避、超时或设计资源获取顺序打破对称性。
  • 实际应用:在分布式系统、数据库事务中广泛使用退避策略(如指数退避)。

六、活锁(Livelock)高频面试题

1. 活锁与死锁的区别是什么?请从线程状态、资源持有、系统表现三方面说明。

答案:

  • 线程状态
    活锁中的线程仍在运行(非阻塞),而死锁中的线程完全阻塞(停止运行)。
  • 资源持有
    活锁中线程会主动释放并重试获取资源,死锁中线程永久占用资源不释放。
  • 系统表现
    活锁导致高 CPU 占用但任务无进展,死锁导致 CPU 闲置且任务完全停滞。

2. 举一个活锁的实际场景或代码示例,并说明其成因。

答案

  • 场景
    两个线程互相释放资源并重试,例如:
    // 线程1:先获取锁A,尝试锁B → 失败 → 释放锁A → 重试  
    // 线程2:先获取锁B,尝试锁A → 失败 → 释放锁B → 重试  
    
  • 成因
    线程的重试策略完全对称(如同时释放和重试),导致无限循环。

3. 如何解决活锁问题?至少给出三种方案并说明原理。

答案

  1. 随机退避:在重试时加入随机延迟(如 Thread.sleep(random.nextInt(100))),打破对称性。
  2. 优先级调度:为线程设置不同的重试优先级(如固定顺序获取资源)。
  3. 超时机制:限制最大重试次数后终止或回退(如 retryCount > MAX_RETRY)。

4. 如何检测系统中的活锁?给出具体方法或工具。

答案

  • 日志分析:观察线程日志中频繁的“获取-释放-重试”循环。
  • 性能监控:高 CPU 使用率但任务完成率为零。
  • 工具检测
    • 使用 jstack 或 VisualVM 查看线程状态(RUNNABLE 但无进展)。
    • APM 工具(如 Arthas)监控方法调用频率。

5. 在设计并发系统时,如何预防活锁?请结合设计原则说明。

答案

  • 资源有序分配:统一资源获取顺序(如总是按 lock1 → lock2 顺序)。
  • 避免对称操作:禁止线程同时释放资源并重试。
  • 退避策略集成:在锁机制中内置随机退避(如数据库事务的指数退避算法)。

总结

这些问题覆盖了活锁的核心概念、实际应用和解决方案,是面试中考察并发编程能力的典型题目。回答时需结合代码示例或系统设计经验以体现深度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/897506.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CodeForces训练记录】Codeforces Round 1009 (Div. 3)

训练情况赛后反思 大翻车,C题打表直接开猜,D题想到了转化行贡献再线段覆盖,但是不会算时间复杂度没敢写,后来试了一发给过了 A题 显然四个点都在坐标轴上,构成正方形只有四个数相等的情况点击查看代码 #include <bits/stdc++.h> // #define int long long #define e…

Cilium Ingress及插件的高级特性

作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 目录一.启用Cilium Ingress Controller1.Cilium Ingress Controller概述2.启用Cilium Ingress Controller3.部署metallb组件4.测试Ingress规则之dedicated模式5.测试Ingress规则之shared模式二.Cilium高级特性…

pycharm基础使用方法

https://blog.csdn.net/MikexunP/article/details/89414928 基本配置 我们安装好PyCharm后,首先要进行一些小配置,比如主题,字体,字体颜色等。 我们打开PyCharm后,点开file,找到Setting 然后就会跳出Setting的窗口 可以说PyCharm的各种配置都需要在这里配置,现在我们进入…

FastAPI 错误处理与自定义错误消息完全指南:构建健壮的 API 应用 ️

title: FastAPI 错误处理与自定义错误消息完全指南:构建健壮的 API 应用 🛠️ date: 2025/3/12 updated: 2025/3/12 author: cmdragon excerpt: 我们将涵盖常见的错误类型、如何捕获和处理这些错误、以及如何返回自定义的错误消息。通过实例和最佳实践,您将能够有效地应对…

基于Flask的Web应用开发

基于Flask的Web应用开发项目来源:[【基于Flask的Web应用开发-01.应用介绍及Flask安装_s】](【基于Flask的Web应用开发-01.应用介绍及Flask安装_s】 https://www.bilibili.com/video/BV1r94y1j7uW/?share_source=copy_web&vd_source=d0886da49a29063777f2956d5780b087) 原…

vitest遇到的一个小问题:scrollTo is not a function

给tdesign-mobile-vue提了一个pr,验收通过了但是单测流水线报错了看报错是组件里的错误,scrollTo is not a function 组件代码的大概逻辑如下 const navScroll = ref<HTMLElement>(); // ... if (navScroll.value) {const tab = navScroll.value.querySelector < HT…

《Python极客编程 : 用代码探索世界》 | PDF免费下载

16个有趣的编程项目,项目式的学习方法,有趣案例提升Python编程技能,培养解决现实问题的思维,附赠源代码,适合零基础计算机小白入门学习。点击下载书籍信息 作者: [美]李沃恩(Lee Vaughan) 出版社: 人民邮电出版社 副标题: 用代码探索世界 译者: 王海鹏 出版年: 2022-8-1…

密度大于0.9408

题目: from Crypto.Util.number import * import random from Crypto.Cipher import AES import hashlib from Crypto.Util.Padding import pad from secrets import flaglist = [] bag = [] p=random.getrandbits(64) assert len(bin(p)[2:])==64 for i in range(4):t = pa=[g…

背包密码密度的作用(LLL算法的实现)

背包问题通解: 当密度d满足以下条件时,LLL算法能有效地解决这类背包问题![](https://cdn.nlark.com/yuque/0/2025/png/49294098/1739349426771-401f2fa7-aefe-422b-b642-5d8f664c415d.png)其中:len(M)是向量M的长度,即向量中元素的数量 max(Mi)是向量M中的最大元素计算: import…

Elliptic(椭圆)曲线

题目: from Crypto.Util.number import getPrime from libnum import s2n from secret import flagp = getPrime(256) a = getPrime(256) b = getPrime(256) E = EllipticCurve(GF(p),[a,b]) m = E.random_point() G = E.random_point() k = getPrime(256) K = k * G r = getPr…

Twisted Hessian曲线(求a)

题目: from Crypto.Util.number import * from Crypto.Cipher import AES from Crypto.Util.Padding import pad from random import randint import hashlib from secrets import flagdef add_THCurve(P, Q):if P == (0, 0):return Qif Q == (0, 0):return Px1, y1 = Px2, y2 …

基于双PI控制器结构的六步逆变器供电无刷直流电机调速simulink仿真

1.课题概述基于双PI控制器结构的六步逆变器供电无刷直流电机调速simulink仿真。双PI控制器是一种结合了两个独立的PI控制器的控制策略,用于提高系统的稳定性和动态性能。2.系统仿真结果 (完整程序运行后无水印) 3.核心程序与模型 版本:MATLAB2022a 4.系统原理简介 4.1 无…