Skip to content

锁是如何实现数据同步的?

锁通过 互斥性(Mutual Exclusion) 实现数据同步,确保同一时刻只有一个线程或进程能访问共享资源,防止并发操作导致的数据不一致。它将并行操作串行化,通过加锁和解锁机制控制访问权限,保障数据的正确性。


关键事实

  1. 目的
  2. 解决竞争条件(Race Condition),如多线程同时修改变量。
  3. 原理
  4. 锁被占用时,其他线程等待,直到锁释放。
  5. 实现
  6. 依赖原子操作(如 CAS)或硬件支持(如 test-and-set)。
  7. 类型
  8. 互斥锁(Mutex)、读写锁、分布式锁等。

实现原理

1. 基本流程

  • 加锁(Lock)
  • 线程尝试获取锁,若成功则独占资源,若失败则等待。
  • 访问共享资源
  • 执行临界区代码,操作数据。
  • 解锁(Unlock)
  • 释放锁,其他线程可竞争获取。

2. 底层机制

  • 原子性
  • 加锁操作不可中断,基于硬件指令(如 cmpxchg)。
  • 示例:Java synchronizedmonitorenter 指令。
  • 状态标记
  • 锁维护状态(如 0=未锁,1=已锁)。
  • 示例:CAS 修改锁状态。
  • 等待队列
  • 未获取锁的线程阻塞或自旋,等待唤醒。
  • 示例:AQS 的 CLH 队列。

3. 示例(Java)

class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) { // 加锁
            count++;         // 临界区
        }                    // 解锁
    }
}
  • 无锁:多线程 count++ 可能丢失更新。
  • 加锁:每次只一个线程修改,count 正确递增。

数据同步的具体实现

1. 互斥锁(Mutex)

  • 原理:只有一个线程持有锁,其他线程阻塞。
  • 场景:保护共享变量。
  • JavasynchronizedReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    count++;
} finally {
    lock.unlock();
}

2. 读写锁

  • 原理:读共享,写独占,允许多线程同时读。
  • 场景:读多写少。
  • JavaReentrantReadWriteLock
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();  // 读锁
rwLock.writeLock().lock(); // 写锁

3. 分布式锁(Redis)

  • 原理:用 Redis 键值对标记锁状态,SETNX 保证互斥。
  • 场景:跨进程同步。
Jedis jedis = new Jedis();
if ("OK".equals(jedis.set("lock", "1", "NX", "PX", 30000))) {
    // 获取锁,操作数据
    jedis.del("lock"); // 释放锁
}

如何保证同步

  1. 互斥性
  2. 锁确保单线程访问,防止并发冲突。
  3. 可见性
  4. 锁释放后,内存屏障(Memory Barrier)刷新数据到主存。
  5. 示例:synchronized 保证变量更新对后续线程可见。
  6. 原子性
  7. 临界区操作不可分割,避免中间状态。

延伸与面试角度

  • 性能影响
  • 锁减少并发度,高竞争下可能瓶颈。
  • 优化:细粒度锁、读写分离。
  • 与无锁对比
  • CAS:无锁但适用简单场景。
  • 锁:阻塞式,适合复杂逻辑。
  • 死锁问题
  • 多锁嵌套可能循环等待。
  • 解决:资源排序、超时。
  • 面试点
  • 问“原理”时,提互斥和原子性。
  • 问“实现”时,提 CAS 或 AQS。

总结

锁通过互斥性串行化访问,用原子操作和等待机制实现同步。Java 用 synchronized 或 AQS,分布式用 Redis 等。面试时,可写 ReentrantLock 示例或提内存屏障,展示理解深度。