深入浅出安卓锁机制

162 阅读4分钟

深入浅出安卓锁机制

一、为什么需要锁?——生活中的锁类比

想象你和室友共用一台洗衣机(共享资源):

  • 问题:你刚按下启动键,室友又按了一次,结果程序混乱
  • 解决方案:给洗衣机加个"使用中"的牌子(锁),一个人用的时候其他人等着

在安卓中同样存在这样的共享资源竞争问题,比如:

  • 多个线程同时修改同一个变量
  • 主线程和网络线程同时操作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) {
        // ...
    }
}

四、死锁问题——交通堵塞现场

典型死锁场景

  1. 线程A持有锁1,等待锁2
  2. 线程B持有锁2,等待锁1
  3. 结果:两个线程永远等待

避免死锁的方法

  • 按固定顺序获取锁
  • 使用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实现

八、总结

安卓锁使用的三大原则:

  1. 能不用锁尽量不用(优先考虑不可变对象/线程隔离)
  2. 用对锁比用好锁更重要(选对锁类型)
  3. 锁的范围越小越好(减小临界区)

记住关键点:

  • synchronized简单但不够灵活
  • ReentrantLock功能强大但要手动释放
  • 读写锁能显著提升读多写少场景性能
  • 死锁就像交通堵塞,要通过规则预防

合理使用锁机制,能让你的APP在多线程环境下既安全又高效!