MST

星途 面试题库

面试题:Java并发编程之锁优化策略

描述一下在高并发场景下,如何对锁进行优化以提高系统性能。例如从锁的粒度控制、锁的类型选择(如偏向锁、轻量级锁、重量级锁)以及锁的使用时机等方面展开说明。
24.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

锁的粒度控制

  1. 减小锁粒度
    • 将大对象或大的业务逻辑拆分成小的部分,每个部分使用单独的锁。例如,在一个电商系统的库存管理中,如果对整个库存对象加锁,并发操作时很多线程会竞争这一把锁。可以将库存按商品类别或者仓库分区,每个分区使用单独的锁,这样不同分区的操作可以并行执行,提高并发度。
    • 以Java的ConcurrentHashMap为例,它在JDK1.8之前采用分段锁机制,将数据分成多个段(Segment),每个段有自己的锁。不同段的操作可以并发进行,相比对整个哈希表加一把大锁,大大提高了并发性能。
  2. 锁粗化
    • 与减小锁粒度相反,当一系列的连续操作都对同一个对象加锁,且加锁解锁操作频繁时,可以适当扩大锁的范围,将多次加锁解锁合并为一次。例如,在一个循环中每次迭代都对同一个对象加锁解锁,可将锁放到循环外部,减少不必要的加锁解锁开销。

锁的类型选择

  1. 偏向锁
    • 适用场景:适用于只有一个线程频繁访问同步块的场景。
    • 原理:偏向锁会偏向于第一个访问同步块的线程,在后续访问中,该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要检查对象头的Mark Word中偏向锁标识和线程ID。如果线程ID是自己的,直接进入同步块,大大减少了锁竞争的开销。
    • 使用注意:在竞争激烈的场景下,偏向锁会带来额外的撤销偏向锁的开销,此时不适合使用偏向锁。可以通过JVM参数 -XX:-UseBiasedLocking 禁用偏向锁。
  2. 轻量级锁
    • 适用场景:适用于竞争不激烈,且同步块执行时间短的场景。
    • 原理:轻量级锁采用CAS操作来尝试获取锁。当线程进入同步块时,JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将对象头的Mark Word复制到锁记录中,然后尝试使用CAS将对象头的Mark Word替换为指向锁记录的指针。如果替换成功,该线程就获得了锁;如果失败,表示存在竞争,轻量级锁会膨胀为重量级锁。
    • 优势:轻量级锁在无竞争或竞争较小的情况下,避免了重量级锁使用操作系统互斥量带来的高开销。
  3. 重量级锁
    • 适用场景:适用于竞争激烈,且同步块执行时间长的场景。
    • 原理:重量级锁依赖于操作系统的互斥量(Mutex)来实现线程同步。当一个线程获取锁时,其他线程会被阻塞,放入等待队列。当持有锁的线程释放锁时,等待队列中的线程会竞争获取锁。
    • 劣势:由于涉及到用户态和内核态的切换,重量级锁的开销较大,在高并发场景下如果大量使用重量级锁会严重影响系统性能。

锁的使用时机

  1. 尽量避免不必要的锁
    • 在代码设计时,仔细分析业务逻辑,确定哪些部分真正需要同步,哪些部分可以异步或者无锁执行。例如,在一个日志记录模块中,如果日志记录操作不需要保证严格的顺序和一致性,完全可以采用异步无锁的方式进行记录,避免对日志写入操作加锁带来的性能损耗。
  2. 提前过滤
    • 在获取锁之前,先进行一些条件判断和过滤操作,减少不必要的锁竞争。比如在一个缓存系统中,当请求数据时,先检查缓存中是否存在数据,如果不存在才去获取锁从数据库加载数据,而不是一开始就获取锁。这样可以避免大部分请求在缓存命中时的锁竞争。
  3. 锁的获取顺序
    • 当多个线程需要获取多个锁时,确保所有线程按照相同的顺序获取锁,避免死锁。例如,线程A和线程B都需要获取锁X和锁Y,那么两个线程都应该先获取锁X,再获取锁Y,这样可以防止死锁的发生,同时也能提高系统的稳定性和性能。