【JAVA基础面经】juc包(java.util.concurrent)


前言

 JUC 提供了比传统 synchronizedwait/notify 更灵活、更高效的并发工具。

一.Callable

 对于传统的 Runnable 接口来说,任务执行完毕后,主线程无法直接获取计算结果,且无法抛出受检异常(run()方法签名未声明 throws,导致异常只能内部消化)。

1.Callable的实现

 Callable 接口则可以解决 Runnable 带来的问题,Callable 是一个泛型接口,定义如下:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {
    public static void main(String[] args) {
        //通过Callable来描述一个任务,泛型参数表示返回值的类型
        Callable<Integer> callable = new Callable<Integer>() {
            //重写callable中的call方法
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for(int i=0;i<=1000;i++){
                    sum += i;
                }
                return sum;
            }
        };
        //为了让线程执行 callable 中的任务,需要创建一个辅助的类对Callable进行封装,线程通过该类获取任务
        FutureTask<Integer> task = new FutureTask<Integer>(callable);
        //创建线程,用来执行任务
        Thread t =new Thread(task);
        t.start();
        //如果线程任务没有执行完成,get就会陷入阻塞
        //会一直阻塞到任务完成,得出计算结果
        try {
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  • call() 方法声明了 throws IOException,因此任务内部可以直接 throw 受检异常,无需 try-catch 包裹。
  • 调用 FutureTask.get() 时,若任务抛出异常,get() 方法会将原始异常包装为 ExecutionException 抛出。

2.Future 接口

 Future 是一个泛型接口,代表异步计算的结果

主线程将任务交给子线程后,可以得到一个Future。主线程可以先做别的事,需要时凭借Future的 get() 即可拿到结果,若线程还没完成则原地等待。

方法描述
boolean cancel(boolean mayInterruptIfRunning)尝试取消任务执行。
boolean isCancelled()判断任务是否已被取消。
boolean isDone()判断任务是否执行完成(正常结束、异常结束或取消均返回 true)。
V get()阻塞获取计算结果,直到任务执行完毕。
V get(long timeout, TimeUnit unit)带超时时间的阻塞获取,超时抛出 TimeoutException。

3.FutureTask

 FutureTask 是 Future 接口的唯一实现类,同时它也实现了 RunnableFuture 接口(该接口继承自 Runnable 和 Future)。

  • Runnable 身份:作为 Thread 构造函数的参数,负责执行 Callable 中的任务逻辑。
  • Future 身份:作为调用方获取结果的句柄,提供 get()、cancel() 等方法。

FutureTask 是连接 Callable 任务与 Thread 线程的桥梁,同时承担了存储异步计算结果与异常的重任。

二.ReentrantLock

 ReentrantLock 是 Java 并发包中提供的一把显式锁,需要开发者手动控制加锁与解锁

1.ReentrantLock的使用

  • lock.lock():解释
  • lock.unlock():解释
ReentrantLock lock = new ReentrantLock();

// 加锁
lock.lock();
try {
    // 受保护的临界区代码
    // 例如:对共享变量进行写操作
} finally {
    // 务必在 finally 块中释放锁,避免因异常导致死锁
    lock.unlock();
}

2.ReentrantLock 与 Synchronized 对比


对比维度synchronized 关键字ReentrantLock 类
实现层面JVM 内置关键字,由 C++ 实现JDK 层面的 Java 类,基于 AQS 框架实现
锁的释放自动释放:代码块执行完毕或抛出异常时,JVM 自动释放锁手动释放:必须在 finally 中显式调用 unlock()
公平策略仅支持非公平锁构造函数支持选择两种锁类型,new ReentrantLock(true) 可创建公平锁
等待可中断线程在等待锁期间只能被动死等,无法被外部打断。一旦陷入阻塞,除非成功获取锁,否则线程将一直处于 BLOCKED 状态,无法响应中断信号提供了 lockInterruptibly() 方法,支持响应中断。当线程 A 在等待锁时,若其他线程调用了 A.interrupt(),线程 A 会立刻从阻塞中醒来,并抛出 InterruptedException,从而让上层代码有机会处理取消逻辑
尝试获取锁一旦锁被占用,当前线程别只能进入阻塞队列无限期等待,直到锁被释放tryLock():一次性的尝试。若锁空闲则立即获取并返回 true;若锁被占用则立即返回 false,线程不会阻塞,可转而执行其他备选逻辑
tryLock(long time, TimeUnit unit):带超时的尝试。在指定时间内反复尝试获取锁,超时未获取则自动放弃并返回 false
条件队列单一 wait/notify/notifyAll 机制支持多个 Condition 条件队列,可精确唤醒特定类型的等待线程
性能表现JDK 1.6 优化后,基础场景下两者性能相近在复杂同步策略或高竞争场景下功能更灵活

4.Condition

 Condition是 ReentrantLock 实现条件等待/唤醒机制的核心组件,与 synchronized 的 wait/notify 相对应。

 可以通过 ReentrantLock.newCondition() 创建

  • Condition.await():await():让当前线程进入条件等待队列,并释放锁。
  • Condition.signal():唤醒一个在该 Condition 上等待的线程。
  • Condition.signalAll():唤醒所有在该 Condition 上等待的线程。

代码例子

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    // 两个不同的条件队列:一个管“不满”,一个管“不空”
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private final int[] buffer = new int[10];
    private int count = 0;  // 当前队列元素个数

    // 生产者方法
    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            // 队列满了,生产者进入 notFull 条件队列等待
            while (count == buffer.length) {
                System.out.println("队列已满,生产者等待...");
                notFull.await();
            }
            // 生产数据
            buffer[count] = value;
            count++;
            System.out.println("生产: " + value + ",当前数量: " + count);
            // 唤醒一个在 notEmpty 条件队列上等待的消费者
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    // 消费者方法
    public int consume() throws InterruptedException {
        lock.lock();
        try {
            // 队列空了,消费者进入 notEmpty 条件队列等待
            while (count == 0) {
                System.out.println("队列已空,消费者等待...");
                notEmpty.await();
            }
            // 消费数据
            int value = buffer[count - 1];
            count--;
            System.out.println("消费: " + value + ",当前数量: " + count);
            // 唤醒一个在 notFull 条件队列上等待的生产者
            notFull.signal();
            return value;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionDemo demo = new ConditionDemo();

        // 启动一个生产者线程
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    demo.produce(i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // 启动一个消费者线程
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    demo.consume();
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

 存在两个等待队列,一个放生产者,一个放消费者。
在这里插入图片描述

3.AQS抽象类

 AQS(AbstractQueuedSynchronizer)是Java中的一个抽象类。 AQS是一个用于构建锁、同步器、协作工具类的工具类,是 JUC 包的基石,是一个用于构建锁、同步器和协作工具类的抽象框架。

ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等并发工具的内部同步器均基于 AQS 实现。

 AQS 通过两大核心组件来管理线程的同步状态:
(1)同步状态 —— volatile int state(被 volatile 修饰的整型变量,用来标识资源的状态)

  • 作用:表示锁的持有情况。
  • 操作:通过 getState()、setState() 以及 CAS 来安全修改状态值。
  • 语义映射(以 ReentrantLock 为例):
    • state == 0:锁空闲。
    • state >= 1:锁已被线程持有。若 state > 1,表示该线程发生了重入。

(2)等待队列 —— CLH 变体队列

  • 数据结构:一个虚拟的 FIFO 双向链表。
  • 节点类型:内部类 Node,封装了等待线程的引用、等待状态(waitStatus)以及前后指针。
  • 工作机制:
    • 入队:当线程尝试获取锁失败时,会被包装成 Node 节点,通过 CAS 操作原子性地加入到队列尾部,随后通过 LockSupport.park() 挂起。
    • 出队:当锁被释放时(state 置 0),AQS 会唤醒队列的头节点后继(通常是第一个有效等待节点),被唤醒的节点再次尝试 CAS 获取锁。

在这里插入图片描述
图片来源

4.ReentrantLock 基于 AQS 的实现

【补图】

synchronized加锁是自适应:无竞争 → 偏向锁 → 轻量级锁(自旋) → 重量级锁(阻塞)

三.原子类 (Atomic 类)

 原子类的内部基于CAS机制实现,性能比加锁实现 i++ 高很多,常用的原子类有如下几个:

类名说明
AtomicBoolean布尔型原子类
AtomicInteger整型原子类
AtomicLong长整型原子类
AtomicIntegerArray长整型原子类
AtomicLong整型数组原子类
AtomicReference引用类型原子类
AtomicStampedReference原子更新带有版本号的引用类型

 以AtomicInteger举例,常见的方法有

addAndGet(int delta);      i += delta
decrementAndGet();   --i
getAndDecrement();   i--
incrementAndGet();  ++i
getAndIncrement();   i++

四.线程池

1.线程池的创建

(1)ExecutorService 和 Executors

 ExecutorService 表示一个线程实例,Executors是一个工厂类,能给个创建出几种不同风格的线程池。通过ExecutorService 中的 submit 方法能够向线程池中提交若干个任务

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
});

Executors创建线程的几种方式

方法说明
newFixedThreadPool创建固定线程数的线程池
newCachedThreadPool创建线程数目动态增长的线程池
newSingleThreadExecutor创建只包含单个线程的线程池
newScheduledThreadPool设定延迟时间后执行命令,或定期执行命令,是进阶版的Timer

(2)ThreadPoolExecutor

 Executors本质上是对ThreadPoolExecutor的封装,ThreadPoolExecutor提供了更多的可选参数,库进一步细化线程池行为的设定,其构造方法如下。

 手动创建 ThreadPoolExecutor 需要指定 7 个核心参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • int corePoolSize 表示核心线程数
  • int maximumPoolSize 表示最大线程数,包括核心线程和非核心线程。
  • long keepAliveTime 表述了非核心线程的存活时间
  • TimeUnit unit 表示keepAliveTime的时间单位
  • BlockingQueue workQueue 任务队列,线程池会提供一个submit方法将任务放入到线程池任务队列中
  • ThreadFactory threadFactory 线程工厂,描述线程是怎么创建出来的
  • RejectedExecutionHandler handler,拒绝策略,当任务队列满了设定的 具体策略,包括忽略最新任务、阻塞等待、丢弃最旧任务等方式

(3)四种常见拒绝策略

策略类行为描述
AbortPolicy (默认)直接抛出 RejectedExecutionException 异常,阻断系统运行。
CallerRunsPolicy由调用者线程(提交任务的线程)执行该任务,起到流量削峰作用。
DiscardPolicy直接丢弃新任务,无任何通知(极度危险,慎用)。
DiscardOldestPolicy丢弃队列中存活最久的任务,然后尝试重新提交新任务。

2.线程池的原理与使用

线程池包含核心线程数、最大线程数以及一个任务等待队列。

  • 核心线程数 (corePoolSize):除非系统空闲回收,否则始终驻留的线程,用于快速响应常态任务量。
  • 最大线程数 (maximumPoolSize):线程池扩容的极限值,用于应对突发峰值流量。
  • 任务等待队列 (workQueue):起到缓冲池的作用,当核心线程繁忙时,任务先进入队列排队等待。

在这里插入图片描述

 下面的代码基于 ThreadPoolExecutor 手动创建

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,                                          // corePoolSize 核心线程数
                5,                                          // maximumPoolSize 最大线程数
                60,                                         // keepAliveTime 空闲存活时间
                TimeUnit.SECONDS,                           // 时间单位
                new ArrayBlockingQueue<>(3),                // 有界任务队列,容量为 3
                Executors.defaultThreadFactory(),           // 默认线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy()   // 拒绝策略:由调用线程执行
        );

        // 2. 提交任务
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务: " + taskId);
                try {
                    Thread.sleep(2000); // 模拟业务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 3. 关闭线程池(不再接收新任务,等待已提交任务执行完毕)
        executor.shutdown();
    }
}

3.线程池的参数设置

1)CPU 密集型任务

  • 主要消耗 CPU 资源进行计算,几乎无阻塞。
  • 核心线程数 = CPU 核心数 + 1。
  • 防止过多线程导致频繁上下文切换,加 1 是为了利用因缺页中断或其他原因暂停的空隙。

(2)IO 密集型任务

  • 频繁进行网络或磁盘读写,线程大部分时间处于阻塞等待状态。
  • 核心线程数 = CPU 核心数 * 2 或使用公式 CPU 核心数 / (1 - 阻塞系数)。
  • 阻塞期间线程不占用 CPU,可多开线程利用 CPU 空闲时间处理其他任务。

(3)通用估算公式

  • 最佳线程数 = CPU 核心数 × 目标 CPU 利用率 × (1 + 平均等待时间 / 平均计算时间)

五、同步工具类

1.CountDownLatch

 CountDownLatch 允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。

方法描述
CountDownLatch(int count)构造函数,设定需要等待的事件数量。
void await()阻塞当前线程,直到计数器的值变为 0。
boolean await(long timeout, TimeUnit unit)带超时的阻塞等待,超时返回 false。
void countDown()计数器减 1。当计数归零时,释放所有等待线程。
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int taskNum = 5;
        CountDownLatch latch = new CountDownLatch(taskNum);

        for (int i = 0; i < taskNum; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    System.out.println("子任务 " + finalI + " 开始执行...");
                    Thread.sleep((long) (Math.random() * 2000));
                    System.out.println("子任务 " + finalI + " 执行完毕!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 无论是否异常,确保计数器递减
                }
            }).start();
        }

        System.out.println("主线程等待所有子任务完成...");
        latch.await(); // 阻塞直到 latch.getCount() == 0
        System.out.println("所有子任务已完成,主线程继续执行!");
    }
}

计数器的值在构造时设定,一旦归零就无法重置。如果需要重复使用,应改用 CyclicBarrier

2.CyclicBarrier

 CyclicBarrier 让一组线程互相等待,直到所有线程都到达一个公共的屏障点(Barrier Point),然后所有线程才继续执行。

方法描述
CyclicBarrier(int parties)构造函数,设定需要拦截的线程数量。
CyclicBarrier(int parties, Runnable barrierAction)当所有线程到达屏障后,优先执行 barrierAction。
int await()阻塞当前线程,等待其他线程到达屏障。返回值为当前线程的到达索引。
void reset()将屏障重置为初始状态(若有等待线程会抛出 BrokenBarrierException)。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int playerNum = 3;
        CyclicBarrier barrier = new CyclicBarrier(playerNum, () -> {
            // 这个 Runnable 由最后一个到达屏障的线程执行
            System.out.println("*** 人齐了,开始匹配游戏!***");
        });

        for (int i = 1; i <= playerNum; i++) {
            int playerId = i;
            new Thread(() -> {
                try {
                    System.out.println("玩家 " + playerId + " 已上线,等待队友...");
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println("玩家 " + playerId + " 已到达大厅,等待其他人...");
                    barrier.await(); // 等待其他人
                    System.out.println("玩家 " + playerId + " 进入游戏房间!");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

计数器归零后会自动重置,可以用于新一轮的等待

支持在所有线程到达后执行一个前置任务(barrierAction)

3.Semaphore

 Semaphore 用于控制同时访问特定资源的线程数量(即限流)

方法描述
Semaphore(int permits)构造函数,设定许可证(permits)数量。
Semaphore(int permits, boolean fair)可设置是否使用公平队列(默认非公平)。
void acquire()获取一个许可,若无可用许可则阻塞等待。
void release()释放一个许可,将其返还给信号量。
boolean tryAcquire()尝试非阻塞获取许可,立即返回结果。
import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
    public static void main(String[] args) {
        int parkingSlots = 3; // 只有 3 个停车位
        Semaphore semaphore = new Semaphore(parkingSlots);

        for (int i = 1; i <= 6; i++) { // 来了 6 辆车
            int carId = i;
            new Thread(() -> {
                try {
                    System.out.println("汽车 " + carId + " 到达停车场入口,等待车位...");
                    semaphore.acquire(); // 申请车位
                    System.out.println("汽车 " + carId + " 成功进入停车场,停车中...");
                    Thread.sleep((long) (Math.random() * 5000));
                    System.out.println("汽车 " + carId + " 驶离停车场。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 务必释放车位
                }
            }).start();
        }
    }
}

常用于数据库连接池、API 限流等场景。

可通过 release(permits) 动态增加许可数量(慎用,通常用于资源扩容)

六、线程安全的集合类

 在多线程环境下,直接使用 ArrayList、HashMap 等非线程安全的集合类会导致数据错乱甚至死循环。Java 标准库及 JUC 包提供了多种线程安全集合方案,下面分别从 List、Queue、Map 三类进行梳理。

1.多线程环境使用List

1.在访问 ArrayList 的代码块外手动添加 synchronized 或 ReentrantLock
2.使用同步包装器 Collections.synchronizedList():

  • Collections.synchronizedList(new ArrayList<>())是标准库中提供的一个基于synchronized进行线程同步的List,synchronizedList 的关键操作上都带有synchronized。

3.使用CopyOnWriteArrayList:

  • 写时拷贝,在修改的时候会创建出副本,对于一个ArrayList{1,2,3,4},如果是多线程读,则不会产生线程安全问题,如果多线程写,则会把ArrayList给复制一份,优先修改副本,这样在修改的同时,对读操作是没有任何影响的,即读的时候先读旧的版本,避免了读到未修改完成的中间状态

2.多线程环境使用Queue

 JUC 提供了丰富的 BlockingQueue 实现,用于解决生产者-消费者场景下的线程安全问题。

队列说明
ArrayBlockingQueue基于数组实现的阻塞队列
LinkedBlockingQueue基于链表实现的阻塞队列
PriorityBlockingQueue基于堆实现的阻塞队列
TransferQueue只包含一个元素的阻塞队列

多线程环境使用Map

1.HashMap本身是线程不安全的,而HashTable 通过给方法直接加锁来保证线程的安全,针对this来进行加锁(针对对象进行加锁),因此当有多个线程访问这个Hashtable的时候,会导致锁竞争的概率非常大,效率较低。

public synchronized V get(Object key)
public synchronized V put(K key, V value) 
...

2.ConcurrentHashMap 操作元素的时候,是针对这个元素所在的链表头结点来进行加锁的(锁桶),如果两个线程针对两个不同链表上的元素进行操作,没有线程安全问题不必要加锁。由于Hash表中,链表的数目非常多,每个链表的长度是相对短的,因此可以保证冲突的概率非常小了

3.ConcurrentHashMap的优点

  • ConcurrentHashMap减少了锁冲突,即锁加在每个链表的头结点上。
  • ConcurrentHashMap只是针对写操作加锁了,读操作没有加锁,只是使用volatie。
  • ConcurrentHashMap更广泛的使用了CAS,进一步提高了效率。
  • .ConcurrentHashMap针对扩容进行了巧妙的化整为零。如果链表过长,就会影响Hash表的效率,扩容就需要创建一个更大的数组,把之前的元素给搬运过去,效率低,对于HashTable来说,只要这次put触发了扩容就一口气搬完,导致这次put非常卡顿。而ConcurrentHashMap每次操作只搬运一点点,通过多从操作完成整个搬运过程,同时维护一个新的 HashMap 和一个旧的 HashMap ,查找的时候既要查找旧的也查找新的,插入的时候只插入新的,直到搬运完毕才销毁旧的

七.面试问题

1.线程获取不到锁会直接进入到阻塞队列吗?

  • synchronized:会先进行自旋等待(默认循环 10 次),自旋期间不进入阻塞队列,如果自旋期间拿到了锁,就继续执行;如果自旋失败或竞争加剧,才会膨胀为重量级锁,此时才会调用操作系统 mutex,线程真正进入阻塞队列(等待队列),状态变为 BLOCKED。
  • ReentrantLock:默认非公平锁的 lock() 方法:先尝试一次 CAS 抢占(插队),失败后判断是否可重入。
    • 若无法重入,则立即将当前线程包装成 Node 节点,通过 CAS 加入 AQS 的 CLH 等待队列尾部,然后调用 LockSupport.park() 挂起。
    • 线程状态为 WAITING,而非 BLOCKED。
    • 若使用 tryLock(),则完全不入队,拿不到锁直接返回 false。

2.CAS 和 AQS 有什么关系?

  • CAS(Compare And Swap)是一种无锁原子操作,通过 CPU 指令保证比较与交换的原子性。
  • AQS(AbstractQueuedSynchronizer)是 JUC 包的基石框架,用于构建锁和同步器。
  • CAS 是 AQS 实现线程安全状态切换的核心工具。
  • AQS 通过 compareAndSetState()(CAS)来安全地修改 volatile int state(锁状态)。
  • 线程入队、出队时,也通过 CAS 保证 CLH 队列的并发安全。
  • CAS 是 AQS 的底层,AQS 是 CAS 在同步框架中的上层封装与应用

3.Condition 和 Object 监视器方法有什么区别

Object 监视器方法Condition
绑定锁类型只能与 synchronized 配合使用必须与 ReentrantLock 配合使用
等待队列数量每个锁对象只有 1 个隐式等待队列每个锁可创建多个显式条件队列
唤醒粒度notify() 随机唤醒一个;notifyAll() 唤醒全部signal() 精准唤醒指定 Condition 上的线程
中断响应wait() 响应中断await() 响应中断;另有 awaitUninterruptibly() 不响应中断
超时等待wait(long timeout)await(long time, TimeUnit unit) 以及 awaitNanos/Until 等丰富方法

4.为什么说 Condition 比 wait/notify 更高效?

  • 复杂同步场景(如生产者-消费者)中,wait/notify 只有一个等待队列,当队列满时生产者阻塞,队列空时消费者阻塞,但两者都会进入同一个等待集合。调用 notifyAll() 会同时唤醒生产者和消费者,导致大量线程被唤醒后因条件不满足而再次休眠,
  • 而 Condition 允许将不同原因的等待线程放入不同队列,当消费者消费数据后,只需要调用 notFull.signal() 唤醒一个生产者,无需打扰其他消费者。
    • 生产者因“队列满”阻塞,进入 notFull 队列。
    • 消费者因“队列空”阻塞,进入 notEmpty 队列。

5.为什么要使用线程池,线程池的作用

  • 降低资源消耗:复用已创建的线程,避免频繁创建/销毁线程带来的 CPU 和内存开销。
  • 提高响应速度:任务到达时,无需等待线程创建过程,可立即由空闲线程执行。
  • 提高线程的可管理性:可统一分配、监控、调优线程数量,防止无限制创建线程导致系统资源耗尽

6.核心线程数设置为0可不可以?

  • 可以,corePoolSize = 0 表示线程池不保留任何核心线程,到达的任务被扔进等待队列,所有线程在空闲超过 keepAliveTime 后都会被回收。

  • 适用场景:

    • 配合 SynchronousQueue:每来一个任务必须立即有一个线程处理,否则任务被拒绝或等待。CachedThreadPool 正是这种配置(核心 0,最大 Integer.MAX_VALUE)。
    • 配合 有界队列:当任务量极低时,可让线程数降为 0,节省资源;任务到达后再创建新线程。
  • 注意事项:若队列不为空且核心线程为 0,任务提交后可能需等待新线程创建,有一定延迟。

7.线程池用了哪些设计模式?

  • 工厂模式:ThreadFactory 接口用于定制线程的创建逻辑(设置名称、优先级、守护状态等)。ThreadPoolExecutor 构造参数里可以传入一个 ThreadFactory。它的作用是:每需要创建一个新线程时,都调用这个工厂来生产
  • 生产者-消费者模式:调用 submit() 的线程是生产者,将任务放入队列;线程池中的工作线程是消费者,从队列中取出任务执行。
  • 策略模式:RejectedExecutionHandler 接口定义了四种拒绝策略(AbortPolicy、CallerRunsPolicy 等),可灵活替换。
  • 模板方法模式:ThreadPoolExecutor 的 beforeExecute() 和 afterExecute() 方法留给子类扩展,实现任务执行前后的钩子逻辑。

8.shutdown和shutdownNow的区别

  • shutdown将线程池状态设置为 SHUTDOWN,shutdownNow设置为 STOP;
  • shutdown仅中断空闲线程,shutdownNow中断所有工作线程(包括正在执行任务的线程);
  • shutdown对于已提交但未执行的任务继续等待执行,shutdownNow则清空队列,并返回未执行的任务列表;
  • shutdown对于已开始执行的任务继续执行至结束,shutdownNow已开始执行的任务被中断。
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

9.提交给线程池中的任务可以被撤回吗?

  • 可以,当向线程池提交任务时,会得到一个Future对象。这个Future对象提供了几种方法来管理任务的执行,包括取消任务。
  • 通过 Future.cancel(),若任务尚未开始执行则标记取消,后续不再执行;若任务正在执行中,那么cancel(true)设置任务必须响应中断,cancel(false)不会中断线程;若任务已完成,则取消失败,返回 false。
Future<?> future = executor.submit(runnable);
future.cancel(true);  // 尝试取消任务
  • 通过 remove() 从队列中移除(仅限未执行任务)
BlockingQueue<Runnable> queue = threadPool.getQueue();
queue.remove(runnable);  // 从等待队列中移除指定任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值