Java面试-04-Java多线程与并发 Java多线程与并发面试题目录1. 创建线程的方式2. 线程生命周期6大状态3. 线程池3.1 创建线程池的方式3.2 为什么不建议用Executors创建线程池3.3 线程池的核心参数3.4 线程池的执行原理3.5 任务类型特点与线程数分配3.6 submit与execute的区别3.7 线程池拒绝策略4. 线程常用API方法5. 程序中怎么保证多线程的运行安全5.1 使用synchronized关键字5.2 使用Lock接口5.3 使用原子类5.4 使用volatile关键字5.5 使用线程安全集合5.6 使用ThreadLocal5.7 避免共享可变状态5.8 使用并发工具类6. Java内存模型JMM7. 并发核心关键字8. 锁机制详解9. CAS与原子类10. ThreadLocal11. 线程安全集合12. 并发队列13. JUC并发工具类14. 死锁与排查1. 创建线程的方式方式一继承Thread类定义一个类继承Thread类重写run方法在run方法中编写线程执行逻辑。使用时创建该类的实例并调用start方法启动线程。这种方式的缺点是Java单继承限制无法再继承其他类。方式二实现Runnable接口定义一个类实现Runnable接口实现run方法。使用时将该类的实例作为参数传给Thread构造器然后调用start方法。这种方式更灵活不影响类继承其他类是推荐的方式。方式三实现Callable接口有返回值定义一个类实现Callable接口实现call方法该方法可以返回结果并抛出异常。使用时需要借助FutureTask包装Callable对象再将FutureTask传给Thread。通过FutureTask的get方法可以获取线程执行的返回值。方式四线程池创建推荐通过Executors工具类创建线程池调用submit方法提交任务。线程池会自动管理线程的创建、复用和销毁适用于高并发场景。使用完毕后调用shutdown方法关闭线程池。四种方式对比方式是否可继承其他类是否有返回值适用场景继承Thread否否简单场景实现Runnable是否推荐灵活实现Callable是是需要返回结果线程池是是高并发场景2. 线程生命周期6大状态新建New创建线程对象但尚未启动就绪Runnable调用start方法后线程进入就绪队列等待CPU调度运行Running获得CPU时间片开始执行run方法中的逻辑阻塞Blocked等待锁、进行IO操作或等待被唤醒等待Waiting调用wait或join方法后进入无限等待状态超时等待TimedWaiting调用sleep等方法后进入限时等待状态终止Terminated线程执行完毕或因异常退出阻塞分类同步阻塞线程争夺synchronized锁失败时进入的阻塞状态等待阻塞调用wait方法后进入等待状态需要notify方法唤醒其他阻塞调用sleep、join方法或进行IO操作时进入的阻塞状态3. 线程池3.1 创建线程池的方式方式一使用Executors工厂方法不推荐通过Executors工具类提供的静态方法创建线程池主要包括固定大小线程池、单线程线程池、缓存线程池和定时任务线程池。这种方式虽然方便但存在资源耗尽的风险。方式二使用ThreadPoolExecutor推荐手动创建ThreadPoolExecutor实例传入七个核心参数核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列、线程工厂和拒绝策略。这种方式可以精确控制线程池的行为避免资源耗尽风险。3.2 为什么不建议用Executors创建线程池方法问题风险newFixedThreadPool/newSingleThreadExecutor使用无界队列LinkedBlockingQueue任务过多时队列无限增长可能OOMnewCachedThreadPool线程数无上限线程过多导致CPU耗尽或OOMnewScheduledThreadPool同样使用无界队列任务过多时队列无限增长结论生产环境推荐使用ThreadPoolExecutor手动配置参数避免资源耗尽风险。3.3 线程池的核心参数参数含义作用corePoolSize核心线程数线程池维护的最小线程数即使空闲也不会回收maximumPoolSize最大线程数线程池允许的最大线程数keepAliveTime空闲线程存活时间非核心线程空闲超过此时间会被回收unit时间单位keepAliveTime的单位秒、毫秒等workQueue任务队列存放等待执行的任务threadFactory线程工厂创建新线程的工厂handler拒绝策略任务无法处理时的处理策略3.4 线程池的执行原理线程池的执行遵循以下流程当提交任务时首先检查核心线程池是否已满如果未满则创建核心线程执行任务如果核心线程已满则检查任务队列是否已满如果未满则将任务加入队列等待如果队列也已满则检查线程池是否达到最大线程数如果未达到则创建非核心线程执行任务如果线程池已满则执行拒绝策略。执行流程详解当提交任务时优先创建核心线程执行核心线程满后任务进入队列等待队列满后创建非核心线程执行所有线程都在忙且队列满时触发拒绝策略3.5 任务类型特点与线程数分配任务类型特点计算公式参数说明CPU密集型大量计算、CPU占用高、IO等待少线程数 CPU核心数 1核心数Runtime.getRuntime().availableProcessors()IO密集型大量IO等待、CPU空闲线程数 CPU核心数 × (1 平均等待时间/平均工作时间)等待时间线程阻塞IO时间工作时间CPU计算时间混合型计算IO都有无固定公式取中间值微调参考IO密集型参数3.6 submit与execute的区别特性executesubmit参数类型RunnableRunnable/CallableT返回值voidFutureT异常处理直接抛出异常被封装到Future中使用场景无需返回结果需要获取执行结果使用示例execute方法用于提交不需要返回结果的任务直接执行即可submit方法用于提交需要返回结果的任务返回一个Future对象通过该对象可以获取任务执行的返回值。3.7 线程池拒绝策略拒绝策略行为适用场景AbortPolicy终止直接抛出异常任务不能丢失必须执行DiscardPolicy丢弃静默丢弃任务任务可丢失不影响业务CallerRunsPolicy调用者执行任务返回给提交者执行调用者可承担执行压力DiscardOldestPolicy丢弃最老丢弃队列最旧任务加入新任务新任务比旧任务更重要自定义拒绝策略实现RejectedExecutionHandler存入DB/缓存延后执行4. 线程常用API方法方法作用currentThread()获取当前线程对象start()启动线程进入就绪态sleep(long)休眠让出CPU不释放锁join()等待该线程执行完毕wait()等待释放锁同步块中notify()随机唤醒一个等待线程notifyAll()唤醒所有等待线程setDaemon(true)设置守护线程必须在start前isAlive()判断线程是否存活setPriority(int)设置优先级1-10默认55. 程序中怎么保证多线程的运行安全5.1 使用synchronized关键字修饰实例方法将synchronized关键字加到实例方法上此时锁的是当前对象实例同一时刻只有一个线程可以执行该方法。修饰静态方法将synchronized关键字加到静态方法上此时锁的是类对象同一时刻只有一个线程可以执行该类的任意静态同步方法。修饰代码块使用synchronized关键字修饰代码块明确指定锁对象可以是this、类对象或其他任意对象只对代码块内的代码进行同步。5.2 使用Lock接口创建Lock接口的实现类实例如ReentrantLock。在需要同步的代码前调用lock方法获取锁在finally块中调用unlock方法释放锁确保锁一定会被释放。Lock接口提供了比synchronized更灵活的锁机制支持公平锁、可中断锁和超时获取锁等特性。5.3 使用原子类使用java.util.concurrent.atomic包下的原子类如AtomicInteger、AtomicLong等。这些类提供了原子操作方法可以在不使用锁的情况下保证操作的原子性通过CAS机制实现线程安全。5.4 使用volatile关键字将共享变量声明为volatile类型可以保证变量的可见性和禁止指令重排序但不能保证操作的原子性。适用于状态标记量等场景。5.5 使用线程安全集合使用java.util.concurrent包下的线程安全集合如CopyOnWriteArrayList、ConcurrentHashMap等。这些集合内部实现了线程安全机制可以在多线程环境下安全地进行操作。5.6 使用ThreadLocal创建ThreadLocal实例来存储线程私有数据每个线程都有自己独立的副本互不干扰。适用于存储用户上下文、数据库连接等需要线程隔离的数据。使用完毕后应调用remove方法避免内存泄漏。5.7 避免共享可变状态通过将可变状态封装在方法内部作为局部变量或者使用不可变对象或者采用消息传递的方式代替共享内存可以从根本上避免线程安全问题。5.8 使用并发工具类使用java.util.concurrent包下的并发工具类如CountDownLatch、CyclicBarrier、Semaphore等可以方便地实现线程间的同步和协作避免手动编写复杂的同步逻辑。5.9 线程安全保障方法对比方法保障特性适用场景synchronized原子性、可见性、有序性通用场景Lock原子性、可见性、有序性需要高级特性中断、超时volatile可见性、有序性状态标记量原子类原子性、可见性计数器、状态标志线程安全集合集合操作线程安全共享集合操作ThreadLocal线程隔离线程私有数据6. Java内存模型JMM什么是JMMJava内存模型统一多线程内存访问规则保证可见性、原子性、有序性。主内存 vs 工作内存主内存所有线程共享存储共享变量工作内存线程私有操作变量副本线程读写流程主内存 → 工作内存 → 主内存JMM三大特性可见性一个线程修改其他线程立即感知保证volatile、synchronized、Lock原子性操作不可中断保证synchronized、原子类有序性禁止指令重排保证volatile、synchronized指令重排编译器/CPU优化执行顺序单线程安全多线程不安全解决方案volatile禁止重排7. 并发核心关键字volatile两大作用保证可见性、禁止指令重排不能保证原子性不能解决i并发问题适用状态标记量、DCL单例synchronized作用保证原子性、可见性、有序性使用修饰实例方法、静态方法、代码块原理加锁自动获取锁解锁自动释放锁释放锁时刷新到主内存获取锁时重读主内存Lock 与 synchronized 区别synchronizedLockJava关键字接口自动加锁/解锁手动lock()/unlock()finally释放非公平锁支持公平/非公平无法中断支持中断无超时机制支持超时获取锁8. 锁机制详解悲观锁 vs 乐观锁悲观锁先加锁再操作synchronized、Lock适用写多读少乐观锁不加锁更新时版本校验CAS、版本号适用读多写少公平锁 vs 非公平锁公平锁按申请顺序获取锁无饥饿效率低非公平锁可插队效率高可能产生饥饿可重入锁同一个线程可再次获取自己持有的锁示例synchronized、ReentrantLock自旋锁获取锁失败时循环重试不阻塞线程优点短时间加锁效率高缺点长时间占用CPU读写锁ReadWriteLock读锁共享多线程可同时读写锁独占写时阻塞所有操作适用读多写少AQSAbstractQueuedSynchronizer定位AQS 是 JUC 并发包的核心基础框架是锁、同步器的统一底层实现ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier、ReentrantReadWriteLock 全部基于 AQS 实现。三大核心组成state 同步状态volatile int用一个volatile修饰的 int 变量保证多线程可见性不同组件含义不同ReentrantLockstate0无锁state≥1持有锁重入次数Semaphore剩余可用许可数CountDownLatch剩余需要倒数的次数双向链表结构的 CLH 变种阻塞队列存储获取锁失败、需要等待的线程节点Node包含线程对象、等待状态、前驱 / 后继指针头节点持有锁的线程或空节点唤醒后继节点尾节点新入队线程追加到尾部独占 / 共享两种模式独占模式EXCLUSIVE同一时间只有一个线程持有锁 → ReentrantLock共享模式SHARED多个线程可同时持有 → Semaphore/CountDownLatch核心原理一句话线程尝试获取锁失败 → 封装成 Node 加入阻塞队列 → 阻塞等待 → 锁释放时唤醒队列头节点线程 → 重新竞争锁关键底层操作CAS 无锁算法修改 state、队列头尾节点不加锁保证原子性LockSupport.park()/unpark()线程阻塞 / 唤醒比 wait/notify 更高效volatile保证 state 和队列节点的内存可见性防止指令重排ReentrantLock 原理1. 核心定位ReentrantLock 是基于 AQS 实现的可重入独占锁支持公平和非公平两种模式2. 公平锁 vs 非公平锁特性公平锁非公平锁锁获取顺序按队列顺序可插队性能较低需维护队列顺序较高饥饿问题无可能产生默认值非公平锁-创建方式创建 ReentrantLock 实例时默认是非公平锁传入 true 参数可以创建公平锁传入 false 参数或不传参数创建非公平锁。3. 锁获取流程非公平锁获取线程尝试获取锁时首先通过 CAS 操作尝试将 state 从 0 设置为 1。如果成功则获取锁如果失败则判断当前线程是否为锁持有者如果是则重入成功state 加 1如果不是则加入阻塞队列等待。公平锁获取线程尝试获取锁时首先检查队列中是否有等待的线程。如果有则直接加入队列等待不插队如果没有则通过 CAS 操作尝试获取锁成功则获取锁失败则加入队列等待。4. 锁释放流程线程调用 unlock 方法时state 减 1。如果 state 减到 0则释放锁并唤醒队列头节点的线程如果 state 大于 0则仅减少重入次数锁仍被当前线程持有。9. CAS与原子类CASCompare-And-Swap无锁原子操作V内存值、A预期值、B新值V A → 更新为B否则不操作是一种无锁机制,Compare-and-Swap比较并交换,CAS操作包含三个操作数 —— 内存位置V、期望的原值A和新值B。执行CAS操作时会将内存位置V的值与期望的原值A进行比较。如果相匹配那么处理器会自动将该内存位置V的值更新为新值B。如果不匹配处理器不做任何操作。CAS缺点ABA问题CAS操作虽然可以确保原子性但存在所谓的“ABA问题”。假设一个线程读取了一个变量的值A然后做了一些计算或等待。在此期间另一个线程将变量的值从A改为B然后又改回A。当第一个线程回来准备使用CAS更新值时它会发现变量的值仍然是A所以CAS操作会成功。然而这实际上是一个问题因为在这段时间内变量的值已经被另一个线程更改过。解决这个问题的常用方法是使用版本号或时间戳即变量不仅保存实际值还保存一个表示何时更改过的标识符。长时间自旋消耗CPU只能保证单个变量原子性原子类java.util.concurrent.atomic类名作用AtomicInteger整数原子操作AtomicLong长整型原子操作AtomicBoolean布尔原子操作AtomicReference引用类型原子操作10. ThreadLocal介绍:ThreadLocal提供线程局部变量为各线程创建独立副本线程操作互不干扰像给线程配私有 “小箱子”。适用场景数据库连接管理为多线程数据库操作提供独立连接应用:Web 应用里保存用户身份信息避免方法间传参。问题:内存泄漏问题ThreadLocalMap键为ThreadLocal弱引用值是强引用。外部强引用消失ThreadLocal被回收键变null值无法回收。解决办法是使用完调用remove()移除键值对。11. 线程安全集合集合实现特点Vectorsynchronized低效基本废弃Hashtablesynchronized低效基本废弃ConcurrentHashMapCAS synchronized高并发、推荐CopyOnWriteArrayList写时复制读多写少CopyOnWriteArraySet写时复制读多写少ConcurrentSkipListMap跳表线程安全、有序12. 并发队列队列类型特点ConcurrentLinkedQueue无界非阻塞高并发、无锁ArrayBlockingQueue有界阻塞数组实现LinkedBlockingQueue无界/有界阻塞链表实现PriorityBlockingQueue优先级阻塞按优先级出队DelayQueue延时阻塞到期才能取出13. JUC并发工具类CountDownLatch计数器减到0唤醒等待线程不可重复使用场景主线程等待多线程完成CyclicBarrier线程相互等待集齐数量一起执行可循环使用场景分组并发执行Semaphore控制最大并发线程数场景接口限流、连接池控制14. 死锁与排查死锁四个必要条件互斥资源独占请求与保持持有锁并等待其他锁不可剥夺锁不能被强抢循环等待线程形成环形等待链避免死锁统一加锁顺序设置锁超时减少锁嵌套死锁排查步骤使用top或ps命令找到Java进程的PID使用jstack命令加上PID参数导出线程栈信息到日志文件在导出的线程栈文件中搜索Found one Java-level deadlock关键字根据线程栈信息定位死锁代码调整加锁顺序解决死锁问题