面试题答案
一键面试1. 锁的粒度调整
- 细化锁粒度:
- 做法:将大的锁保护区域划分为多个小的锁保护区域。例如,在一个管理用户信息的系统中,如果原本使用一把锁保护整个用户信息表的读写操作,可根据用户ID的范围划分多个小区域,每个区域使用一把单独的锁。这样不同区域的操作可以并发进行,提升并发性能。
- 适用场景:当操作对象可以进行逻辑划分,且不同部分的操作相互独立时适用。比如在数据库的分库分表场景下,每个表或者每个分区可以使用单独的锁,不同表或分区的操作不会相互阻塞。
- 粗化锁粒度:
- 做法:与细化相反,将多个小的锁保护区域合并为一个大的锁保护区域。例如,在一个简单的计数器场景中,如果每次对计数器的增减操作都加锁,频繁的锁竞争会导致性能问题。此时可以将多个操作合并,在一段连续操作开始前加锁,结束后解锁。
- 适用场景:当多个操作紧密相关,且频繁加解锁带来的开销大于锁竞争带来的开销时适用。比如在一些数据的批量处理场景中,对一组相关数据的一系列操作可以用一把锁来保护。
2. 锁的类型选择
- 读写锁:
- 做法:当读操作远多于写操作时,使用读写锁。读操作时多个线程可以同时获取读锁,共享数据;写操作时只允许一个线程获取写锁,防止数据不一致。例如在一个新闻发布系统中,大量用户读取新闻内容(读操作),而编辑发布或修改新闻(写操作)相对较少。
- 适用场景:适用于读多写少的场景。因为读操作不修改数据,多个线程同时读不会产生数据不一致问题,读写锁允许并发读,大大提升了并发性能。
- 自旋锁:
- 做法:当线程获取锁失败时,不立即进入阻塞状态,而是在原地自旋一段时间,不断尝试获取锁。例如在一个多核CPU的系统中,线程竞争锁的时间较短,自旋等待锁被释放,避免线程上下文切换的开销。
- 适用场景:适用于锁被持有时间较短,且CPU资源较为充足的场景。如果锁被持有时间过长,自旋会浪费CPU资源,此时使用普通锁更合适。
- 乐观锁:
- 做法:乐观锁假设在大多数情况下,数据的并发访问不会产生冲突。在更新数据时,先检查数据在读取后是否被其他线程修改。例如通过版本号机制,每次数据更新时版本号递增,读取数据时记录版本号,更新时对比版本号,如果一致则更新成功,否则重试。
- 适用场景:适用于数据冲突概率较低的场景。由于乐观锁不需要在每次访问时加锁,减少了锁竞争和加解锁的开销,在冲突少的情况下能显著提升性能。
- 悲观锁:
- 做法:悲观锁假设数据在并发访问时一定会产生冲突,所以每次访问数据时都加锁,防止其他线程访问。例如在银行转账操作中,对账户余额的操作必须加锁,防止同时转账导致余额错误。
- 适用场景:适用于数据一致性要求极高,数据冲突概率较大的场景。确保任何时候只有一个线程能访问关键数据,保证数据的准确性和一致性。