面试题答案
一键面试- 使用不同的锁来控制不同的资源:
- 任务队列锁:
ThreadPoolExecutor
使用ReentrantLock
来保护任务队列。例如,当向任务队列添加任务(execute
方法中添加任务到workQueue
)或者从任务队列获取任务(getTask
方法从workQueue
获取任务)时,会获取这个锁。这使得对任务队列的并发访问得到控制,避免多个线程同时修改任务队列导致数据不一致。 - 线程池状态锁:线程池状态(如
RUNNING
、SHUTDOWN
等)的改变通过AtomicInteger
类型的ctl
变量来控制。ctl
不仅存储线程池状态,还存储线程数量信息。由于AtomicInteger
是基于硬件级别的原子操作实现的,在更新线程池状态时不需要像普通变量那样使用锁,从而减少了锁竞争。例如,在shutdown
方法中改变线程池状态为SHUTDOWN
时,通过ctl
的原子操作来实现。
- 任务队列锁:
- 基于CAS(Compare - and - Swap)操作:
- 在一些关键操作中,如线程池状态改变和线程数量调整,使用了CAS操作。例如,在
addWorker
方法中尝试增加工作线程时,会使用compareAndIncrementWorkerCount
方法,这个方法内部基于AtomicInteger
的compareAndSet
操作来原子地增加工作线程数量。如果当前线程池状态和预期状态一致,就会更新线程数量,否则重试。这种方式避免了使用锁来进行线程数量的增减操作,减少了锁竞争。
- 在一些关键操作中,如线程池状态改变和线程数量调整,使用了CAS操作。例如,在
- 分离读和写操作:
- 对于线程池状态等一些读多写少的操作,尽量做到读操作无锁化。由于
ctl
变量是AtomicInteger
类型,读操作(如获取线程池状态、线程数量等)不需要加锁,因为AtomicInteger
的读操作是线程安全的。只有在写操作(如改变线程池状态、增减线程数量)时才使用原子操作或者锁机制,这样分离读写操作,减少了读操作时的锁竞争,提高了并发性能。
- 对于线程池状态等一些读多写少的操作,尽量做到读操作无锁化。由于