深入浅出安卓锁机制
一、为什么需要锁?——生活中的锁类比
想象你和室友共用一台洗衣机(共享资源):
- 问题:你刚按下启动键,室友又按了一次,结果程序混乱
- 解决方案:给洗衣机加个"使用中"的牌子(锁),一个人用的时候其他人等着
在安卓中同样存在这样的共享资源竞争问题,比如:
- 多个线程同时修改同一个变量
- 主线程和网络线程同时操作UI
- 多个服务同时读写数据库
二、安卓中的锁基本类型
1. synchronized——最简单的门锁
// 用法1:锁方法(锁住整个方法)
public synchronized void safeMethod() {
// 临界区代码
}
// 用法2:锁代码块(更精确控制)
public void safeMethod() {
synchronized(this) { // this是锁对象
// 临界区代码
}
}
特点:
- 自动加锁/解锁
- 同一时间只允许一个线程进入
- 锁对象可以是任意Java对象
2. ReentrantLock——高级密码锁
private final ReentrantLock lock = new ReentrantLock();
public void safeMethod() {
lock.lock(); // 手动上锁
try {
// 临界区代码
} finally {
lock.unlock(); // 必须手动解锁(放在finally确保执行)
}
}
优势:
- 可尝试获取锁:
tryLock() - 可设置公平锁(先到先得)
- 支持锁中断
3. ReadWriteLock——图书馆规则
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 读操作(允许多线程同时读)
public String readData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
// 写操作(独占锁)
public void writeData(String newData) {
writeLock.lock();
try {
data = newData;
} finally {
writeLock.unlock();
}
}
适用场景:
- 读多写少的情况(如缓存系统)
- 数据库读写分离
三、安卓特有锁机制
1. Handler+Looper——消息队列锁
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 这里自动获得主线程锁
}
};
本质:利用消息队列的串行特性实现线程安全
2. LiveData的线程安全
liveData.observe(this) { value ->
// 自动在主线程执行
}
背后原理:内部使用synchronized保证数据同步
3. Room数据库的锁
@Dao
public interface UserDao {
@Insert
void insert(User user); // Room自动处理线程安全
@Transaction // 开启事务(原子操作)
public void updateUsers(List<User> users) {
// ...
}
}
四、死锁问题——交通堵塞现场
典型死锁场景:
- 线程A持有锁1,等待锁2
- 线程B持有锁2,等待锁1
- 结果:两个线程永远等待
避免死锁的方法:
- 按固定顺序获取锁
- 使用
tryLock()设置超时 - 减少锁的粒度
// 错误示范(可能死锁)
synchronized(lockA) {
synchronized(lockB) {
// ...
}
}
// 正确做法(固定顺序)
synchronized(lockA) {
synchronized(lockB) {
// ...
}
}
五、性能优化技巧
1. 减小锁粒度
// 不好:锁整个方法
public synchronized void updateAll() {
updateA();
updateB();
}
// 好:只锁必要部分
public void updateAll() {
synchronized(this) { updateA(); }
synchronized(this) { updateB(); }
}
2. 使用并发集合
// 代替手动加锁的ArrayList
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// 更高效的并发集合
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
3. 避免锁嵌套
// 危险代码(容易死锁)
synchronized(lock1) {
synchronized(lock2) {
// ...
}
}
六、实际应用场景
1. 单例模式(双重检查锁)
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意:必须使用volatile防止指令重排序
2. 线程安全的计数器
public class SafeCounter {
private int count;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
3. 异步任务同步
private final Object lock = new Object();
private boolean isDataReady = false;
// 线程A:准备数据
synchronized(lock) {
prepareData();
isDataReady = true;
lock.notifyAll();
}
// 线程B:等待数据
synchronized(lock) {
while (!isDataReady) {
lock.wait();
}
useData();
}
七、锁的性能对比
| 锁类型 | 适用场景 | 性能 | 特点 |
|---|---|---|---|
synchronized | 简单同步 | 中等 | JVM内置支持 |
ReentrantLock | 复杂控制 | 高 | 功能丰富 |
ReadWriteLock | 读多写少 | 读快写慢 | 读写分离 |
| 原子变量 | 计数器等 | 最高 | CAS实现 |
八、总结
安卓锁使用的三大原则:
- 能不用锁尽量不用(优先考虑不可变对象/线程隔离)
- 用对锁比用好锁更重要(选对锁类型)
- 锁的范围越小越好(减小临界区)
记住关键点:
synchronized简单但不够灵活ReentrantLock功能强大但要手动释放- 读写锁能显著提升读多写少场景性能
- 死锁就像交通堵塞,要通过规则预防
合理使用锁机制,能让你的APP在多线程环境下既安全又高效!