Android锁

news2025/5/16 19:22:37

引言 🔒

在 Android 应用的开发过程中,随着业务需求的复杂度不断提升,多线程并发场景层出不穷。为了保证数据一致性与线程安全,锁(Lock)成为了不可或缺的工具。本篇博客将深入剖析 Android 中常用的锁机制、使用场景与最佳实践,并配以精炼的示例代码与示意图,帮助你快速掌握锁的精髓。


一、锁的基本概念

1. 什么是锁?

锁是一种同步机制,用于控制多个线程对共享资源的访问,确保在同一时刻只有一个线程能够进入临界区(Critical Section),从而避免数据竞争与不一致。

2. 锁的作用

  • 互斥访问:保证同一资源在同一时刻仅被一个线程修改。
  • 可见性保证:在释放锁后,修改对其他线程可见。
  • 阻塞与等待:线程无法获取锁时,会被挂起或进入等待队列,直至锁可用。

3. 为什么在 Android 开发中需要锁?

- UI 线程与后台线程交互需保证同步;
- 缓存、数据库、网络调用等资源共享需防止数据竞争;
- 多进程组件(ContentProvider、AIDL)之间的同步需求。


二、线程与并发基础知识回顾 🧵

在深入锁之前,先回顾 Android 常用的线程与并发工具:

  1. Thread:直接创建线程,使用 new Thread(runnable).start()
  2. Handler & Looper:用于在特定线程(通常是主线程)中传递消息与任务。
  3. HandlerThread:封装了 Looper 的后台线程。
  4. ExecutorService(线程池):包括 ThreadPoolExecutorScheduledThreadPoolExecutor 等,可复用线程,控制并发度。
// 简单线程池示例
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> {
    // 后台任务
});
pool.shutdown();

🔍 图示:线程模型示意图(伪代码注释提示)

[UI线程] <--Handler--> [HandlerThread] ---> ThreadPool

三、Java 中的锁机制 🧩

3.1 synchronized

  • 定义:Java 语言关键字,隐式获取对象或类的监视器锁。
  • 语法:方法锁、代码块锁。
// 方法锁
public synchronized void doWork() { ... }

// 块锁
synchronized (lockObject) {
    // 临界区
}

✅ 优点:语法简洁,隐式释放锁;
⚠️ 缺点:不可中断、不支持公平锁、功能有限。

3.2 ReentrantLock

  • 定义:Java java.util.concurrent.locks 包中的可重入锁。
  • 特性:支持公平锁、可中断锁、尝试加锁(tryLock())。
ReentrantLock lock = new ReentrantLock(true); // fair = true
try {
    lock.lockInterruptibly();
    // 临界区
} finally {
    lock.unlock();
}

🔄 可重入性:同一线程可多次获取锁;
⏰ 可中断:获取锁时可响应中断。

3.3 ReadWriteLock

  • 定义:读写分离锁,允许多个读线程并发,写线程独占。
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

readLock.lock();
try { ... } finally { readLock.unlock(); }

writeLock.lock();
try { ... } finally { writeLock.unlock(); }

📖 场景:读多写少的数据访问,如缓存读取。

3.4 Semaphore

  • 定义:计数信号量,控制同时访问某资源的线程数。
Semaphore sem = new Semaphore(3);
sem.acquire();
try { ... } finally { sem.release(); }

🎛️ 场景:限制并发连接数、线程池实现原理。

3.5 CountDownLatch

  • 定义:线程间等待工具,等待其他线程执行完毕后继续。
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        // 任务
        latch.countDown();
    }).start();
}
latch.await(); // 等待所有子线程完成

🔐 场景:并行准备+主线程等待。

3.6 CyclicBarrier

  • 定义:可循环使用的屏障,等待一组线程到达同一点后再继续。
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    // 全部线程到达后执行
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 工作
        barrier.await();
    }).start();
}

🚧 场景:多线程分阶段计算。


四、锁在 Android 开发中的典型应用场景 🧠

4.1 多线程更新 UI 的同步

  • 问题:Android UI 只能在主线程更新,后台线程需切换。
  • 方案:使用 runOnUiThread()Handlersynchronized 等保证顺序。
synchronized (uiLock) {
    runOnUiThread(() -> {
        // 更新 UI
    });
}

💡 示例图:UI线程与后台线程交互时序图。

4.2 数据缓存的并发访问控制

  • 场景:内存缓存如 LruCache,多线程同时读取和写入。
  • 方案ReadWriteLock 分离读写;或 ConcurrentHashMap
private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();

public Bitmap get(String key) {
    cacheLock.readLock().lock();
    try { return cache.get(key);}
    finally { cacheLock.readLock().unlock(); }
}

4.3 多进程同步

  • 场景:使用 ContentProvider 或 AIDL 实现进程间通信(IPC),需要同步访问共享资源。
  • 方案:在 ContentProvider 中使用数据库事务锁;在 AIDL 服务端使用 synchronizedReentrantLock

📦 图示:AIDL 服务多进程调用时序图。

4.4 网络请求和数据库操作的线程安全处理

  • 网络请求:使用 OkHttp 自带连接池,限制并发;
  • 数据库:Room 默认支持多线程读写,事务内操作自动加锁;
  • 手动加锁:对执行顺序敏感时,可使用 Semaphore 控制并发量。
Semaphore netSem = new Semaphore(10);
netSem.acquire();
try {
    Response resp = okHttpClient.newCall(request).execute();
} finally {
    netSem.release();
}

五、synchronized vs ReentrantLock 对比 📦

特性synchronizedReentrantLock
公平性不可选公平可选公平(fair 参数)
可中断不可中断支持 lockInterruptibly()
尝试加锁不支持支持 tryLock()
条件变量wait/notifyCondition 对象
性能JDK 自动优化需要手动释放锁

六、死锁与避免技巧 🔁

6.1 死锁的形成条件

  1. 互斥条件;
  2. 占有且等待;
  3. 不可剥夺;
  4. 循环等待;

6.2 常见案例

synchronized(a) {
    synchronized(b) { ... }
}

synchronized(b) {
    synchronized(a) { ... }
}

6.3 预防方法

  • 锁顺序:统一的资源加锁顺序;
  • 定时锁尝试tryLock(timeout)
  • 资源分离:减少互斥区域;
  • 死锁检测:借助工具(MAT、ThreadMXBean)。

七、最佳实践建议 💡

  1. 尽量避免锁:优先使用无锁数据结构(ConcurrentHashMapAtomicXXX);
  2. 控制粒度:最小化同步块;
  3. 读写分离:多读少写场景优先 ReadWriteLock
  4. 使用线程池:减少线程创建销毁成本;
  5. 文档与注释:明确锁的作用与风险;
  6. 监控与调优:借助工具监测锁竞争;

八、实用示例代码 🧪

8.1 高性能缓存示例

public class SafeCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public V get(K key) {
        lock.readLock().lock();
        try { return cache.get(key);}
        finally { lock.readLock().unlock(); }
    }

    public void put(K key, V value) {
        lock.writeLock().lock();
        try { cache.put(key, value);}
        finally { lock.writeLock().unlock(); }
    }
}

注释:读写分离,保证多线程并发读取高效。

8.2 定时死锁尝试示例

ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

void safeOperation() {
    try {
        if (lockA.tryLock(500, TimeUnit.MILLISECONDS)) {
            try {
                if (lockB.tryLock(500, TimeUnit.MILLISECONDS)) {
                    try {
                        // 安全操作
                    } finally { lockB.unlock(); }
                }
            } finally { lockA.unlock(); }
        }
    } catch (InterruptedException ignored) {}
}

九、高级内容拓展 🚀

9.1 Kotlin 协程与锁机制对比

  • 协程调度:轻量级线程,挂起而非阻塞;
  • Mutex:协程专用锁,支持 lockwithLock
val mutex = Mutex()
suspend fun safeWrite() {
    mutex.withLock {
        // 协程内安全写操作
    }
}

📊 对比表:传统锁 vs 协程 Mutex

9.2 使用 Channel 替代锁

  • 场景:消息传递代替共享内存;
val channel = Channel<Int>()
launch {
    for (msg in channel) { println(msg) }
}
launch {
    channel.send(42)
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

bfs-最小步数问题

最小步长模型 特征&#xff1a; 主要是解决权值为1且状态为字符串类型的最短路问题&#xff0c;实质上是有向图的最短路问题&#xff0c;可以简化为bfs求最短路问题。 代表题目&#xff1a; acwing 845 八数码问题&#xff1a; 八数码题中由于每次交换的状态是由x进行上下左右…

java----------->代理模式

目录 什么是代理模式&#xff1f; 为什么会有代理模式&#xff1f; 怎么写代理模式&#xff1f; 实现代理模式总共需要三步&#xff1a; 什么是代理模式&#xff1f; 代理模式&#xff1a;给目标对象提供一个代理对象&#xff0c;并且由代理对象控制目标对象的引用 代理就是…

Untiy基础学习(十四)核心系统—物理系统之碰撞检测代码篇 刚体,碰撞体,材质

目录 一、碰撞器&#xff08;Collider&#xff09;与触发器&#xff08;Trigger&#xff09; 二、碰撞检测条件 三、碰撞事件与触发器事件&#xff0c;可以理解为特殊的生命周期函数。 四、讲讲如何选择 ​编辑 五、总结 一、碰撞/触发事件函数对照表 二、Collider 与 …

SAP学习笔记 - 开发08 - Eclipse连接到 BTP Cockpit实例

有关BTP&#xff0c;之前学了一点儿&#xff0c;今天继续学习。 SAP学习笔记 - 开发02 - BTP实操流程&#xff08;账号注册&#xff0c;BTP控制台&#xff0c;BTP集成开发环境搭建&#xff09;_sap btp开发-CSDN博客 如何在Eclipse中连接BTP Cockpit开发环境实例。 1&#xf…

Git的安装和配置(idea中配置Git)

一、Git的下载和安装 前提条件&#xff1a;IntelliJ IDEA 版本是2023.3 &#xff0c;那么配置 Git 时推荐使用 Git 2.40.x 或更高版本 下载地址&#xff1a;CNPM Binaries Mirror 操作&#xff1a;打开链接 → 滚动到页面底部 → 选择2.40.x或更高版本的 .exe 文件&#xf…

【2025版】Spring Boot面试题

文章目录 1. Spring, Spring MVC, SpringBoot是什么关系&#xff1f;2. 谈一谈对Spring IoC的理解3. Component 和 Bean 的区别&#xff1f;4. Autowired 和 Resource 的区别&#xff1f;5. 注入Bean的方法有哪些&#xff1f;6. 为什么Spring 官方推荐构造函数注入&#xff1f;…

火山引擎实时音视频 高代码跑通日志

实时音视频 SDK 概览--实时音视频-火山引擎 什么是实时音视频 火山引擎实时音视频&#xff08;Volcengine Real Time Communication&#xff0c;veRTC&#xff09;提供全球范围内高可靠、高并发、低延时的实时音视频通信能力&#xff0c;实现多种类型的实时交流和互动。 通…

jenkins 启动报错

java.lang.UnsatisfiedLinkError: /opt/application/jdk-17.0.11/lib/libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory。 解决方案&#xff1a; yum install freetype-devel 安装完成之后重启jenkins。

【合新通信】无人机天线拉远RFOF(射频光纤传输)解决方案

无人机天线拉远RFOF方案通过光纤替代传统射频电缆&#xff0c;实现无人机与地面控制站之间的高保真、低损耗信号传输&#xff0c;尤其适用于高频段&#xff08;如毫米波&#xff09;、远距离或复杂电磁环境下的无人机作业场景。 核心应用场景 军事侦察与电子战 隐蔽部署&…

程序设计语言----软考中级软件设计师(自用学习笔记)

目录 1、解释器和编译器 2、程序的三种控制结构 3、程序中的数据必须具有类型 4、编译、解释程序翻译阶段 5、符号表 6、编译过程 7、上下文无关文法 8、前、中、后缀表达式 9、前、后缀表达式计算 10、语法树中、后序遍历 11、脚本语言和动态语言 12、语法分析方法…

通过SMTP协议实现Linux邮件发送配置指南

一、环境准备与基础配置 1. SMTP服务开通&#xff08;以qq邮箱为例&#xff09; 登录qq邮箱网页端&#xff0c;进入「设置」-「POP3/SMTP/IMAP」 开启「SMTP服务」并获取16位授权码&#xff08;替代邮箱密码使用&#xff09; 记录关键参数&#xff1a; SMTP服务器地址&#…

若依框架页面

1.页面地址 若依管理系统 2.账号和密码 管理员 账号admin 密码admin123 运维 账号yuwei 密码123456 自己搭建的地址方便大家学习&#xff0c;不要攻击哦&#xff0c;谢谢啊

44、私有程序集与共享程序集有什么区别?

私有程序集&#xff08;Private Assembly&#xff09;与共享程序集&#xff08;Shared Assembly&#xff09;是.NET框架中程序集部署的两种不同方式&#xff0c;它们在部署位置、版本控制、访问权限等方面存在显著差异&#xff0c;以下是对二者的详细比较&#xff1a; 1. 部署…

【Java面试题】——this 和 super 的区别

&#x1f381;个人主页&#xff1a;User_芊芊君子 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f50d;系列专栏&#xff1a;【Java】内容概括 【前言】 在Java的世界里&#xff0c;this和 super是两个非常重要且容易混淆的关键字。无论是在日常…

CentOS 7 内核升级指南:解决兼容性问题并提升性能

点击上方“程序猿技术大咖”&#xff0c;关注并选择“设为星标” 回复“加群”获取入群讨论资格&#xff01; CentOS 7 默认搭载的 3.10.x 版本内核虽然稳定&#xff0c;但随着硬件和软件技术的快速发展&#xff0c;可能面临以下问题&#xff1a; 硬件兼容性不足&#xff1a;新…

解决 PicGo 上传 GitHub图床及Marp中Github图片编译常见难题指南

[目录] 0.行文概述 1.PicGo图片上传失败 2.*关于在Vscode中Marp图片的编译问题* 3.总结与启示行文概述 写作本文的动机是本人看到了Awesome Marp&#xff0c;发现使用 Markdown \texttt{Markdown} Markdown做PPT若加持一些 CSS , JavaScript \texttt{CSS},\texttt{JavaScript} …

软考 系统架构设计师系列知识点之杂项集萃(59)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;58&#xff09; 第96题 假设关系模式R(U, F)&#xff0c;属性集U{A, B, C}&#xff0c;函数依赖集F{A->B, B->C}。若将其分解为p{R1(U1, F1), R2(U2, F2)&#xff0c;其中U1{A, B}, U2{A, …

python使用matplotlib画图

【README】 plot画图有两种方法&#xff1a;包括 plt.plot(), ax.plot()-画多个子图 &#xff0c;其中ax表示某个坐标轴; 【1】画单个图 import matplotlib # 避免兼容性问题&#xff1a;明确指定 matplotlib 使用兼容的后端&#xff0c;TkAgg 或 Agg matplotlib.use(TkAgg) …

upload-labs通关笔记-第5关 文件上传之.ini绕过

目录 一、ini文件绕过原理 二、源码审计 三、渗透实战 1、查看提示 2、制作.user.ini文件 &#xff08;1&#xff09;首先创建一个文本文件 &#xff08;2&#xff09;保存文件名为.user.ini 2、制作jpg后缀脚本 &#xff08;1&#xff09;创建一个文本文件 &#xf…

ssti模板注入学习

ssti模板注入原理 ssti模板注入是一种基于服务器的模板引擎的特性和漏洞产生的一种漏洞&#xff0c;通过将而已代码注入模板中实现的服务器的攻击 模板引擎 为什么要有模板引擎 在web开发中&#xff0c;为了使用户界面与业务数据&#xff08;内容&#xff09;分离而产生的&…