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:异常处理。
