MST

星途 面试题库

面试题:Java ThreadPoolExecutor内部锁的竞争优化

在ThreadPoolExecutor中,当多个线程同时访问和修改线程池状态及任务队列时,内部锁机制是如何进行优化以减少锁竞争的?请详细说明其实现原理。
12.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

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