【Java核心技术/多线程】35道Java多线程面试题与答案
1. 什么是线程?线程与进程的区别是什么?
- 答案:线程是进程内的执行单元,是CPU调度的最小单位;进程是资源分配的最小单位,一个进程可包含多个线程。
- 核心区别:进程间资源独立,线程共享进程的内存/文件句柄等资源,线程切换成本远低于进程。
2. Java中创建线程的3种方式是什么?
- 答案:
继承 Thread 类,重写 run() 方法;
实现 Runnable 接口,重写 run() 方法,将实例传给 Thread ;
实现 Callable 接口,重写 call() 方法(可返回结果、抛异常),结合 FutureTask 使用。
3. start() 方法和 run() 方法的区别?
- 答案: start() 会启动新线程,由JVM调用 run() ;直接调用 run() 只是普通方法调用,不会创建新线程,仍在当前线程执行。
4. 线程的生命周期有哪些状态?
- 答案:Java线程生命周期(6种状态,定义在 Thread.State 枚举中)
NEW :线程创建后未启动;
RUNNABLE :就绪(等待CPU)或运行中;
BLOCKED :等待同步锁(如 synchronized 未获取到);
WAITING :无超时等待(如 wait() 、 join() ),需被唤醒;
TIMED_WAITING :有超时等待(如 wait(1000) 、 sleep(1000) );
TERMINATED :线程执行完毕。
5. synchronized 关键字的作用是什么?有几种用法?
- 答案:作用是保证多线程对共享资源的原子性、可见性、有序性(防止指令重排序),实现同步。
- 用法:
修饰普通方法:锁是当前对象实例;
修饰静态方法:锁是当前类的Class对象;
修饰代码块:锁是括号内的指定对象/类(如 synchronized (this) {} )。
6. synchronized 和 volatile 的区别?
- 答案:
- volatile :仅保证可见性和有序性,不保证原子性,用于修饰变量;
- synchronized :保证原子性、可见性、有序性,可修饰方法/代码块,是重量级锁(JDK 1.6后优化为轻量级锁、偏向锁)。
7. volatile 的原理是什么?为什么能保证可见性?
- 答案:原理是“内存屏障”:
写 volatile 变量时,会在操作后加“写屏障”,强制将本地内存的修改刷到主内存;
读 volatile 变量时,会在操作前加“读屏障”,强制从主内存加载最新值到本地内存;
由此保证多线程间变量的可见性,且禁止指令重排序(如单例双重检查的 instance 需加 volatile )。
8. 什么是原子类?Java提供了哪些原子类?
- 答案:原子类是通过CAS(Compare and Swap)实现无锁原子操作的类,避免 synchronized 的性能开销,位于 java.util.concurrent.atomic 包。
常见原子类:
基本类型: AtomicInteger 、 AtomicLong 、 AtomicBoolean ;
引用类型: AtomicReference 、 AtomicStampedReference (解决ABA问题);
数组类型: AtomicIntegerArray 、 AtomicLongArray 。
9. CAS是什么?有什么缺点?
- 答案:CAS(Compare and Swap,比较并交换)是无锁同步机制,核心是“先比较内存值是否等于预期值,若相等则更新为新值,否则不操作”。
- 缺点:
ABA问题:内存值从A→B→A,CAS会误判为未修改(可通过 AtomicStampedReference 加版本号解决);
自旋开销:CAS失败后会循环重试,高并发下占用CPU;
只能保证单个变量原子性:无法实现多变量组合操作的原子性。
10. 什么是AQS?它的核心原理是什么?
- 答案:AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发工具的基础框架(如 ReentrantLock 、 CountDownLatch ),用于实现锁和同步器。
- 核心原理:
- 维护一个volatile int state(同步状态,如0=未锁定,1=锁定)和一个双向阻塞队列(存储等待线程);
- 子类通过重写 tryAcquire() (获取锁)、 tryRelease() (释放锁)等方法,控制 state 的修改;
- 线程获取锁失败时,会被封装为节点加入队列,然后阻塞;释放锁时,唤醒队列头节点的线程。
11. ReentrantLock 是什么?它与 synchronized 的区别?
- 答案: ReentrantLock 是JDK提供的可重入独占锁,基于AQS实现,需手动 lock() 和 unlock() (通常在 try-finally 中)。
- 与 synchronized 的区别:
维度 synchronized ReentrantLock
锁获取/释放 自动(进入方法/代码块时获取,退出时释放) 手动(需显式调用 lock() / unlock() )
可中断性 不可中断 可中断( lockInterruptibly() )
超时获取 不支持 支持( tryLock(long timeout) )
公平锁 非公平(默认),无法指定 可指定公平/非公平(构造函数传 true )
条件变量 仅1个(通过 wait() / notify() ) 多个(通过 newCondition() 创建)
12. 什么是可重入锁? synchronized 和 ReentrantLock 是可重入的吗?
- 答案:可重入锁(递归锁)指线程获取锁后,可再次获取同一把锁而不被阻塞(即锁会记录持有线程和重入次数,释放时次数减为0才真正释放)。
- 是, synchronized 和 ReentrantLock 均为可重入锁。例如:一个 synchronized 方法调用另一个 synchronized 方法,同一线程不会死锁。
13. 什么是公平锁和非公平锁? ReentrantLock 默认是哪种?
- 答案:
- 公平锁:线程获取锁的顺序与请求顺序一致(先到先得),避免线程饥饿,但性能较低;
- 非公平锁:线程获取锁时不按请求顺序,可“插队”(刚释放的锁可能被新请求的线程获取),性能高,但可能导致线程饥饿;
- ReentrantLock 默认是非公平锁,构造函数传 true 可创建公平锁。
14. 什么是死锁?产生死锁的4个必要条件是什么?
- 答案:死锁是多个线程互相持有对方需要的锁,且都不释放,导致所有线程永久阻塞的状态。
- 4个必要条件(缺一不可):
- 互斥条件:资源只能被一个线程占用;
- 持有并等待:线程持有一个资源,同时等待另一个资源;
- 不可剥夺:线程已持有的资源不能被强制剥夺;
- 循环等待:多个线程形成“资源请求循环”(如A等B的锁,B等A的锁)。
15. 如何避免死锁?
- 答案:破坏死锁的4个必要条件之一即可:
- 破坏“循环等待”:给所有锁按固定顺序编号,线程按编号顺序获取锁;
- 破坏“持有并等待”:一次性获取所有需要的锁,获取失败则释放已持有的锁;
- 破坏“不可剥夺”:使用 tryLock(timeout) ,超时则释放已持有的锁;
- 破坏“互斥条件”:使用可共享的资源(如读写锁的读锁)。
16. Thread.sleep(long millis) 和 Object.wait() 的区别?
- 答案:
- 所属类不同: sleep() 是 Thread 的静态方法; wait() 是 Object 的实例方法;
- 锁释放不同: sleep() 不释放当前持有的锁; wait() 会释放锁,直到被 notify() / notifyAll() 唤醒;
- 唤醒方式不同: sleep() 到时间自动唤醒; wait() 需显式唤醒(或超时自动唤醒);
- 使用场景不同: sleep() 用于暂停执行; wait() 用于线程间通信(如生产者-消费者)。
17. notify() 和 notifyAll() 的区别?
- 答案:
- notify() :随机唤醒当前对象等待队列中的一个线程,使其进入就绪状态;
- notifyAll() :唤醒当前对象等待队列中的所有线程,使其进入就绪状态;
- 注意:两者都需在 synchronized 代码块/方法中调用,否则抛 IllegalMonitorStateException 。
18. 什么是线程池?为什么要用线程池?
- 答案:线程池是管理线程的“容器”,预先创建一批线程,复用线程执行任务,避免频繁创建/销毁线程的开销。
- 核心优势:
- 降低资源消耗:复用线程,减少线程创建/销毁的CPU和内存开销;
- 提高响应速度:任务到达时,无需等待线程创建,直接复用现有线程;
- 便于管理:统一控制线程数量、任务队列大小,避免线程过多导致系统资源耗尽。
19. Java线程池的核心参数有哪些?( ThreadPoolExecutor 的7个参数)
- 答案: ThreadPoolExecutor 的构造函数核心参数(7个):
- corePoolSize :核心线程数(线程池长期维持的线程数量,即使空闲也不销毁);
- maximumPoolSize :最大线程数(线程池可创建的最多线程数);
- keepAliveTime :非核心线程的空闲存活时间(超过该时间则销毁非核心线程);
- unit : keepAliveTime 的时间单位(如 TimeUnit.SECONDS );
- workQueue :任务阻塞队列(核心线程满时,新任务放入队列等待);
- threadFactory :创建线程的工厂(可自定义线程名称、优先级等);
- handler :拒绝策略(线程池和队列都满时,如何处理新任务)。
20. 线程池的拒绝策略有哪些?
- 答案:Java默认提供4种拒绝策略(实现 RejectedExecutionHandler 接口):
- AbortPolicy (默认):直接抛 RejectedExecutionException ,拒绝任务;
- CallerRunsPolicy :由提交任务的调用者线程执行任务(避免任务丢失,缓解线程池压力);
- DiscardPolicy :默默丢弃新任务,不抛异常;
- DiscardOldestPolicy :丢弃队列中最旧的任务(队列头部任务),然后尝试提交新任务。
21. 线程池的任务执行流程是什么?
- 答案:线程池处理新任务的步骤(核心逻辑):
- 若当前线程数 < corePoolSize :创建核心线程执行任务;
- 若当前线程数 ≥ corePoolSize :检查任务队列 workQueue ,若队列未满,将任务放入队列;
- 若队列已满:检查当前线程数 < maximumPoolSize ,创建非核心线程执行任务;
- 若当前线程数 ≥ maximumPoolSize :触发拒绝策略处理任务。
22. Executors 提供的常见线程池有哪些?为什么不推荐使用?
- 答案: Executors 是线程池工具类,提供4种预设线程池:
- newFixedThreadPool(n) :固定核心线程数的线程池( corePoolSize=maximumPoolSize=n ,队列无界);
- newSingleThreadExecutor() :单线程线程池( corePoolSize=maximumPoolSize=1 ,队列无界);
- newCachedThreadPool() :缓存线程池( corePoolSize=0 , maximumPoolSize=Integer.MAX_VALUE ,队列同步移交);
- newScheduledThreadPool(n) :定时任务线程池(核心线程数 n ,支持定时/周期性任务);
- 不推荐原因:
- FixedThreadPool / SingleThreadExecutor 的队列是无界的( LinkedBlockingQueue ),任务过多时会导致OOM;
- CachedThreadPool 的最大线程数是 Integer.MAX_VALUE ,高并发下会创建大量线程,导致OOM。
推荐:直接使用 ThreadPoolExecutor 手动指定参数,明确线程数和队列大小,避免资源耗尽。
23. 什么是读写锁? ReentrantReadWriteLock 的特点是什么?
- 答案:读写锁是分离“读操作”和“写操作”的锁,允许多个线程同时读,仅允许一个线程写,提高读多写少场景的并发性能。
- ReentrantReadWriteLock 的特点:
- 可重入:读线程可再次获取读锁,写线程可再次获取写锁/读锁;
- 公平/非公平:支持公平锁(构造函数传 true )和非公平锁(默认);
- 写锁独占:写锁被获取时,其他线程无法获取读锁/写锁;
- 读锁共享:读锁被获取时,其他线程可获取读锁,但无法获取写锁;
- 降级:写锁可降级为读锁(写线程获取读锁后释放写锁);
- 不可升级:读锁不可升级为写锁(避免死锁)。
24. 什么是CountDownLatch?它的使用场景是什么?
- 答案: CountDownLatch 是基于AQS的同步工具,通过“计数器”实现:主线程等待多个子线程完成任务后,再继续执行。
- 核心逻辑:
- 初始化时指定计数器值(如 new CountDownLatch(3) 表示等待3个任务);
- 子线程完成任务后调用 countDown() ,计数器减1;
- 主线程调用 await() ,阻塞直到计数器变为0;
- 使用场景:如主线程等待多个子线程加载资源完成后,再执行后续逻辑。
25. 什么是CyclicBarrier?它与CountDownLatch的区别?
- 答案: CyclicBarrier (循环屏障)是基于AQS的同步工具,让多个线程到达“屏障点”后,再一起继续执行,且屏障可重复使用。
- 与 CountDownLatch 的区别:
维度 CountDownLatch CyclicBarrier
计数器是否可重置 不可重置(计数器到0后失效) 可重置( reset() 方法,屏障可循环使用)
线程角色 分“等待线程”和“计数线程”(主线程等子线程) 所有线程都是“等待线程”(线程间互相等待)
触发条件 计数器到0 到达屏障的线程数达到预设值
用途 1个线程等多个线程完成 多个线程互相等待,协同执行
26. 什么是Semaphore?它的使用场景是什么?
- 答案: Semaphore (信号量)是基于AQS的同步工具,通过“许可数”控制同时访问共享资源的线程数量。
- 核心逻辑:
- 初始化时指定许可数(如 new Semaphore(5) 表示允许5个线程同时访问);
- 线程获取资源前调用 acquire() ,获取1个许可(许可数减1,无许可则阻塞);
- 线程释放资源后调用 release() ,释放1个许可(许可数加1);
- 使用场景:限流(如控制同时访问数据库的连接数)、实现线程池等。
27. 什么是ThreadLocal?它的原理是什么?
- 答案: ThreadLocal 是线程本地变量,让每个线程拥有独立的变量副本,避免多线程共享变量的并发问题。
- 原理:
- 每个 Thread 对象内部维护一个 ThreadLocalMap (键: ThreadLocal 实例,值:变量副本);
- 线程调用 ThreadLocal.set(value) 时,将 value 存入当前线程的 ThreadLocalMap ;
- 线程调用 ThreadLocal.get() 时,从当前线程的 ThreadLocalMap 中获取值;
- 线程销毁时, ThreadLocalMap 也会销毁,避免内存泄漏(但需注意:若线程复用,需手动 remove() ,否则副本会残留)。
28. ThreadLocal可能导致内存泄漏的原因是什么?如何避免?
- 答案:内存泄漏原因: ThreadLocalMap 的键是 ThreadLocal 的弱引用,值是强引用。当 ThreadLocal 实例被回收(弱引用失效),键变为 null ,但值仍被 ThreadLocalMap 引用,若线程长期存活(如线程池复用),值无法被GC回收,导致内存泄漏。
- 避免方式:
- 使用完 ThreadLocal 后,手动调用 remove() 方法,清除 ThreadLocalMap 中的键值对;
- 避免使用静态 ThreadLocal (静态变量生命周期长,更易导致泄漏)。
29. 什么是线程安全?如何保证线程安全?
- 答案:线程安全指多线程并发访问共享资源时,程序的执行结果与单线程执行结果一致,且无数据损坏、死锁等问题。
- 保证方式:
- 锁机制: synchronized 、 ReentrantLock 、读写锁等;
- 无锁机制:原子类( AtomicInteger )、CAS;
- 线程本地变量: ThreadLocal (避免共享变量);
- 不可变对象:使用 final 修饰变量(对象一旦创建,状态不可修改,天然线程安全)。
30. 什么是不可变对象?为什么不可变对象是线程安全的?
- 答案:不可变对象指对象创建后,其状态(成员变量的值)无法修改的对象(如 String 、 Integer )。
- 线程安全原因:不可变对象的状态不会被修改,多线程访问时无需担心“一个线程修改,另一个线程读取”的并发问题,因此天然线程安全。
31. 什么是线程间通信?Java中实现线程间通信的方式有哪些?
- 答案:线程间通信指多个线程通过共享资源或特定机制,协调执行顺序、传递数据的过程。
- 实现方式:
- synchronized + wait() / notify() / notifyAll() ;
- ReentrantLock + Condition ( await() / signal() / signalAll() );
- 管道流: PipedInputStream / PipedOutputStream (线程间传递字节流);
- 共享变量: volatile 修饰的变量(保证可见性,实现简单通信);
- 同步工具: CountDownLatch 、 CyclicBarrier (协调线程执行顺序)。
32. Condition 接口的作用是什么?它与 wait() / notify() 的区别?
- 答案: Condition 是 ReentrantLock 的条件变量,用于实现线程间的精准通信,可类比 synchronized 中的 wait() / notify() 。
- 与 wait() / notify() 的区别:
- 条件变量数量:一个 ReentrantLock 可创建多个 Condition (实现多条件等待); synchronized 仅对应一个条件变量(对象本身);
- 唤醒精度: Condition 的 signal() 可唤醒指定条件队列的线程; notify() 只能随机唤醒一个线程;
- 功能丰富度: Condition 支持 awaitUninterruptibly() (不可中断等待)、 awaitUntil(Date) (截止时间等待), wait() 无此功能。
33. 什么是线程的中断?如何中断一个线程?
- 答案:线程中断是向线程发送“中断信号”,通知线程需要停止执行,但线程是否停止由自身决定(不会强制终止)。
- 中断相关方法:
- thread.interrupt() :给线程发送中断信号,设置线程的“中断状态”为 true ;
- Thread.currentThread().isInterrupted() :判断当前线程的中断状态(不会清除状态);
- Thread.interrupted() :判断当前线程的中断状态(会清除状态,将状态重置为 false );
- 注意:若线程处于 WAITING / TIMED_WAITING 状态,调用 interrupt() 会使线程抛出 InterruptedException ,并清除中断状态。
34. 什么是Fork/Join框架?它的核心思想是什么?
- 答案:Fork/Join框架是Java 7引入的并行计算框架,基于“分治法”,将大任务拆分为多个小任务并行执行,最后合并小任务的结果。
- 核心思想:
- Fork(拆分):将大任务拆分为多个可并行执行的小任务;
- Join(合并):等待所有小任务执行完毕,合并小任务的结果,得到大任务的最终结果;
- 核心类: ForkJoinPool (线程池)、 ForkJoinTask (任务抽象类,子类 RecursiveTask 有返回值, RecursiveAction 无返回值)。
35. 什么是CompletableFuture?它的作用是什么?
- 答案: CompletableFuture 是Java 8引入的异步编程工具,基于Future框架增强,支持链式调用、异步回调、任务组合,解决了传统 Future 需阻塞等待结果的问题。
- 核心作用:
- 异步执行任务:无需手动创建线程,可指定线程池(默认用 ForkJoinPool.commonPool() );
- 链式回调:通过 thenApply() / thenAccept() / thenRun() 等方法,在任务完成后自动执行回调逻辑;
- 任务组合:通过 thenCombine() / allOf() / anyOf() 等方法,组合多个异步任务的结果;
- 异常处理:通过 exceptionally() / handle() 方法,优雅处理异步任务的异常。