面试题答案
一键面试锁的粒度控制
- 减小锁粒度:
- 将大对象或大的业务逻辑拆分成小的部分,每个部分使用单独的锁。例如,在一个电商系统的库存管理中,如果对整个库存对象加锁,并发操作时很多线程会竞争这一把锁。可以将库存按商品类别或者仓库分区,每个分区使用单独的锁,这样不同分区的操作可以并行执行,提高并发度。
- 以Java的ConcurrentHashMap为例,它在JDK1.8之前采用分段锁机制,将数据分成多个段(Segment),每个段有自己的锁。不同段的操作可以并发进行,相比对整个哈希表加一把大锁,大大提高了并发性能。
- 锁粗化:
- 与减小锁粒度相反,当一系列的连续操作都对同一个对象加锁,且加锁解锁操作频繁时,可以适当扩大锁的范围,将多次加锁解锁合并为一次。例如,在一个循环中每次迭代都对同一个对象加锁解锁,可将锁放到循环外部,减少不必要的加锁解锁开销。
锁的类型选择
- 偏向锁:
- 适用场景:适用于只有一个线程频繁访问同步块的场景。
- 原理:偏向锁会偏向于第一个访问同步块的线程,在后续访问中,该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要检查对象头的Mark Word中偏向锁标识和线程ID。如果线程ID是自己的,直接进入同步块,大大减少了锁竞争的开销。
- 使用注意:在竞争激烈的场景下,偏向锁会带来额外的撤销偏向锁的开销,此时不适合使用偏向锁。可以通过JVM参数
-XX:-UseBiasedLocking
禁用偏向锁。
- 轻量级锁:
- 适用场景:适用于竞争不激烈,且同步块执行时间短的场景。
- 原理:轻量级锁采用CAS操作来尝试获取锁。当线程进入同步块时,JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将对象头的Mark Word复制到锁记录中,然后尝试使用CAS将对象头的Mark Word替换为指向锁记录的指针。如果替换成功,该线程就获得了锁;如果失败,表示存在竞争,轻量级锁会膨胀为重量级锁。
- 优势:轻量级锁在无竞争或竞争较小的情况下,避免了重量级锁使用操作系统互斥量带来的高开销。
- 重量级锁:
- 适用场景:适用于竞争激烈,且同步块执行时间长的场景。
- 原理:重量级锁依赖于操作系统的互斥量(Mutex)来实现线程同步。当一个线程获取锁时,其他线程会被阻塞,放入等待队列。当持有锁的线程释放锁时,等待队列中的线程会竞争获取锁。
- 劣势:由于涉及到用户态和内核态的切换,重量级锁的开销较大,在高并发场景下如果大量使用重量级锁会严重影响系统性能。
锁的使用时机
- 尽量避免不必要的锁:
- 在代码设计时,仔细分析业务逻辑,确定哪些部分真正需要同步,哪些部分可以异步或者无锁执行。例如,在一个日志记录模块中,如果日志记录操作不需要保证严格的顺序和一致性,完全可以采用异步无锁的方式进行记录,避免对日志写入操作加锁带来的性能损耗。
- 提前过滤:
- 在获取锁之前,先进行一些条件判断和过滤操作,减少不必要的锁竞争。比如在一个缓存系统中,当请求数据时,先检查缓存中是否存在数据,如果不存在才去获取锁从数据库加载数据,而不是一开始就获取锁。这样可以避免大部分请求在缓存命中时的锁竞争。
- 锁的获取顺序:
- 当多个线程需要获取多个锁时,确保所有线程按照相同的顺序获取锁,避免死锁。例如,线程A和线程B都需要获取锁X和锁Y,那么两个线程都应该先获取锁X,再获取锁Y,这样可以防止死锁的发生,同时也能提高系统的稳定性和性能。