面试题答案
一键面试1. 使用重入锁实现任务原子性和顺序性
在分布式系统中,利用ThreadPoolExecutor
执行任务时,为保证任务执行的原子性和顺序性,可使用重入锁。
- Java 代码示例:
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DistributedTask {
private static final ReentrantLock lock = new ReentrantLock();
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
public static void executeTask(Runnable task) {
lock.lock();
try {
executor.submit(task);
} finally {
lock.unlock();
}
}
}
- 原理:通过在任务提交前获取重入锁,只有获取到锁的节点才能提交任务到线程池执行。当一个节点执行完任务释放锁后,其他节点才有机会获取锁并提交任务,从而保证了任务执行的原子性和顺序性。
2. 分布式锁一致性挑战及应对策略
- 挑战:在分布式环境下,多个节点可能同时尝试获取锁,由于网络延迟、节点故障等原因,可能导致不同节点对锁的状态认知不一致。例如,节点 A 认为锁已释放,而节点 B 由于网络问题还未收到锁释放的消息,仍然认为锁被占用。
- 应对策略:
- 使用分布式协调服务(如Zookeeper):Zookeeper 利用其树形结构和顺序节点特性来实现分布式锁。每个节点尝试创建一个顺序临时节点,序号最小的节点获得锁。当持有锁的节点故障或释放锁时,Zookeeper 会通知下一个序号的节点获取锁,保证了锁的一致性。
- 使用 Redis 实现分布式锁:利用 Redis 的单线程特性和 SETNX 命令(SET if Not eXists)。获取锁时,使用 SETNX 命令设置一个锁的 key-value,若设置成功则获取锁,否则获取失败。释放锁时,通过 Lua 脚本保证释放操作的原子性,避免误释放其他节点的锁。同时,设置锁的过期时间,防止节点崩溃导致锁无法释放。
3. 性能瓶颈挑战及应对策略
- 挑战:重入锁在分布式系统中可能成为性能瓶颈。每次获取和释放锁都涉及网络通信(如使用分布式协调服务或 Redis 实现分布式锁时),大量的锁操作会增加网络开销,降低系统整体性能。此外,若锁的粒度设置不当,会导致线程竞争激烈,降低并发度。
- 应对策略:
- 优化锁粒度:尽量将锁的粒度细化,只对关键的共享资源加锁。例如,将一个大任务拆分成多个小任务,每个小任务对应不同的锁,减少锁的竞争范围。
- 锁的分级:根据任务的优先级或重要性,设置不同级别的锁。高优先级任务获取高级别的锁,低优先级任务获取低级别的锁,避免高优先级任务被低优先级任务阻塞。
- 采用乐观锁:在一些场景下,若数据冲突概率较低,可采用乐观锁机制。通过版本号或时间戳等方式,在更新数据时检查数据是否被其他线程修改,若未修改则更新成功,否则重试。这样减少了锁的持有时间,提高了并发性能。