Java多线程面试题精选

Java多线程是面试中的高频考点,以下整理了经典面试题及参考答案。

1. 创建线程有几种方式?

Java中创建线程主要有四种方式:

  • 继承Thread类:继承Thread类并重写run()方法,通过start()启动线程。
  • 实现Runnable接口:实现Runnable接口的run()方法,传入Thread构造器。推荐使用,因为Java单继承多实现。
  • 实现Callable接口:Callable可以有返回值和抛出异常,配合FutureTask使用。
  • 线程池:通过ExecutorService创建线程池,复用线程提高效率。

2. synchronized和ReentrantLock的区别?

  • 实现方式:synchronized是JVM层面的关键字,ReentrantLock是API层面的类。
  • 锁释放:synchronized自动释放锁,ReentrantLock需手动unlock(),通常在finally中释放。
  • 可中断性:synchronized不可中断,ReentrantLock可通过lockInterruptibly()响应中断。
  • 公平锁:synchronized是非公平锁,ReentrantLock支持公平和非公平两种模式。
  • 条件绑定:ReentrantLock支持多个Condition条件,synchronized只有一个等待队列。

3. volatile关键字的作用和原理?

volatile保证共享变量的可见性和禁止指令重排序,但不保证原子性。

  • 可见性:volatile变量的写操作会立即刷新到主内存,读操作从主内存读取,保证线程间可见。
  • 禁止重排序:通过插入内存屏障(Memory Barrier)防止编译器和处理器对指令进行重排。
  • 不保证原子性:如i++操作不是原子的,需要用AtomicInteger或synchronized。
  • 底层原理:写操作时会生成Lock前缀指令,将当前处理器缓存行写回主内存,并通过缓存一致性协议(MESI)使其他CPU缓存失效。

4. ThreadLocal原理及内存泄漏问题?

ThreadLocal为每个线程提供变量副本,实现线程间隔离。每个Thread对象中有一个ThreadLocalMap,key是ThreadLocal的弱引用,value是实际值。

  • 内存泄漏原因:key是弱引用,GC后key变为null,但value是强引用不会被回收。如果线程不结束(如线程池),value就无法回收。
  • 解决方案:使用完后调用remove()清除数据。
  • InheritableThreadLocal:子线程可以获取父线程的ThreadLocal值。

5. 线程池核心参数和工作流程?

  • corePoolSize:核心线程数,即使空闲也不会回收(除非设置allowCoreThreadTimeOut)。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:非核心线程的空闲存活时间。
  • workQueue:任务队列,常用LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue。
  • threadFactory:线程工厂,用于创建线程。
  • rejectedExecutionHandler:拒绝策略,有AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最旧)。

工作流程:核心线程满 → 任务进队列 → 队列满 → 创建非核心线程到最大线程数 → 仍满 → 执行拒绝策略。

6. synchronized的锁升级过程?

  • 无锁状态:对象头Mark Word初始状态。
  • 偏向锁:第一个线程访问时,在Mark Word中记录线程ID。同一线程再次进入只需CAS判断。
  • 轻量级锁:多线程竞争但无激烈冲突,通过CAS自旋获取锁。
  • 重量级锁:自旋失败或竞争激烈,升级为重量级锁,通过Monitor(操作系统层面的互斥量)实现,线程阻塞唤醒。

7. AQS(AbstractQueuedSynchronizer)原理?

AQS是Java并发包的核心框架,ReentrantLock、Semaphore、CountDownLatch等都基于AQS实现。

  • 核心结构:一个volatile int state表示同步状态,一个FIFO双向队列存储等待线程。
  • 独占模式:ReentrantLock使用,state表示锁被重入的次数。
  • 共享模式:Semaphore/CountDownLatch使用,state表示剩余许可数。
  • 获取锁:tryAcquire()尝试获取,失败则封装为Node入队,park阻塞。前驱节点释放时unpark后继节点。

8. 什么是死锁?如何排查和预防?

死锁:两个或多个线程互相持有对方需要的锁,导致永久阻塞。

  • 四个必要条件:互斥、持有并等待、不可剥夺、循环等待。
  • 排查工具:jstack输出线程dump,查找”Found one Java-level deadlock”;jconsole可视化检测;Arthas的thread命令。
  • 预防:固定锁顺序、使用tryLock设置超时、避免嵌套锁、使用并发工具类替代手动加锁。

9. CountDownLatch、CyclicBarrier、Semaphore的区别?

  • CountDownLatch:一次性使用,countDown()减1,await()等待到0。用于主线程等待子任务完成。
  • CyclicBarrier:可重复使用,await()等待到指定数量后一起放行,可执行barrierAction。用于多线程同步。
  • Semaphore:信号量,acquire()获取许可,release()释放。用于限流。

10. CompletableFuture的用法?

CompletableFuture是Java 8引入的异步编程工具,支持链式调用、组合多个异步任务。

  • supplyAsync/runAsync:异步执行任务。
  • thenApply/thenAccept/thenRun:链式处理结果。
  • thenCompose:串联两个异步任务。
  • allOf/anyOf:等待所有/任一任务完成。
  • exceptionally/handle:异常处理。

By admin

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注