ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java 线程相关 面试知识

Java 线程相关 面试知识 1. 引言Java 多线程是后端开发面试中的高频考点几乎每一场 Java 岗位面试都会涉及。无论是应届生还是资深工程师线程相关的问题都是考察并发编程功底的核心。本文从线程的基础概念出发逐步深入到 JMM、锁机制、线程池、并发工具类等进阶内容帮助读者系统梳理 Java 线程相关的面试知识点。2. 线程基础2.1 进程与线程的区别进程操作系统资源分配的基本单位拥有独立的地址空间。线程CPU 调度的基本单位是进程内的一个执行路径共享进程的资源。核心区别进程之间相互独立通信成本高线程之间共享内存通信方便但需要处理同步问题。2.2 创建线程的四种方式继承 Thread 类classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(继承 Thread 方式);}}newMyThread().start();实现 Runnable 接口Runnabletask()-System.out.println(实现 Runnable 方式);newThread(task).start();实现 Callable 接口有返回值CallableIntegercallable()-42;FutureTaskIntegerfutureTasknewFutureTask(callable);newThread(futureTask).start();IntegerresultfutureTask.get();线程池创建ExecutorServiceexecutorExecutors.newFixedThreadPool(4);executor.submit(()-System.out.println(线程池方式));面试追问推荐使用 Runnable 或 Callable 方式因为 Java 是单继承实现接口更灵活且便于解耦任务与执行。2.3 线程的生命周期Java 线程共有六种状态Thread.State枚举NEW新建尚未调用start()。RUNNABLE就绪或运行中。BLOCKED等待获取监视器锁synchronized 阻塞。WAITING无限期等待如wait()、join()。TIMED_WAITING限期等待如sleep(1000)、wait(1000)。TERMINATED终止。状态流转图start()synchronized 竞争失败获取锁wait()/join()notify()/notifyAll()sleep()/wait(timeout)时间到/notify()run() 执行完毕NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED3. 线程安全与 synchronized3.1 什么是线程安全当多个线程同时访问某个共享资源时如果不需要额外的同步控制程序执行的结果仍然正确则称该资源是线程安全的。3.2 synchronized 的三种用法修饰实例方法锁是当前实例对象。publicsynchronizedvoidmethod(){}修饰静态方法锁是当前类的 Class 对象。publicstaticsynchronizedvoidstaticMethod(){}修饰代码块锁是括号内指定的对象。synchronized(lockObject){// 临界区}3.3 synchronized 的底层原理Monitor 机制每个对象都有一个 Monitor监视器锁线程进入synchronized代码块时需获取 Monitor 的所有权。字节码层面monitorenter和monitorexit指令。锁升级无锁 → 偏向锁 → 轻量级锁 → 重量级锁JDK 1.6 优化后。3.4 volatile 关键字可见性保证变量修改对所有线程立即可见底层通过 MESI 缓存一致性协议或内存屏障实现。禁止重排序插入内存屏障防止指令重排。不保证原子性volatile不能替代synchronized如i操作仍非原子。典型应用双重检查锁DCL单例模式。classSingleton{privatestaticvolatileSingletoninstance;privateSingleton(){}publicstaticSingletongetInstance(){if(instancenull){synchronized(Singleton.class){if(instancenull){instancenewSingleton();}}}returninstance;}}4. JMMJava 内存模型4.1 内存模型概述JMM 定义了线程与主内存之间的抽象关系主内存所有线程共享存储共享变量。工作内存每个线程私有存储变量的副本。4.2 三大特性特性说明实现手段原子性操作不可分割synchronized、Lock、Atomic 类可见性一个线程修改对其它线程可见volatile、synchronized、final有序性禁止指令重排序volatile、synchronized、happens-before4.3 happens-before 原则程序次序规则单线程内按代码顺序。锁规则解锁 happens-before 后续加锁。volatile 规则写 happens-before 后续读。传递性A happens-before BB happens-before C则 A happens-before C。5. 锁机制进阶5.1 Lock 接口与 ReentrantLockLocklocknewReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();}与 synchronized 对比对比项synchronizedReentrantLock锁获取隐式显式可中断否是lockInterruptibly公平锁否可指定条件变量wait/notifyCondition性能JDK 1.6 后相近相近5.2 公平锁与非公平锁公平锁按请求顺序获取锁性能略低。非公平锁允许插队吞吐量更高ReentrantLock默认非公平。5.3 悲观锁与乐观锁悲观锁假设冲突必然发生先加锁再操作synchronized、Lock。乐观锁假设冲突很少先操作再验证CAS、版本号。5.4 CAS 与 Atomic 类CASCompare And Swap包含三个操作数内存位置 V、预期值 A、新值 B。仅当 V 的值等于 A 时才将 V 更新为 B。AtomicIntegercountnewAtomicInteger(0);count.incrementAndGet();// 底层使用 CASCAS 的三大问题ABA 问题可用版本号解决、自旋开销、只能保证单个变量的原子性。6. 线程池6.1 为什么使用线程池降低资源消耗复用线程减少创建销毁开销。提高响应速度任务到达时无需等待创建线程。提高线程可管理性统一分配、调优和监控。6.2 线程池核心参数newThreadPoolExecutor(corePoolSize,// 核心线程数maximumPoolSize,// 最大线程数keepAliveTime,// 空闲线程存活时间TimeUnit.SECONDS,// 时间单位workQueue,// 任务队列threadFactory,// 线程工厂handler// 拒绝策略);6.3 任务执行流程否是否是否是提交任务核心线程是否已满?创建核心线程执行任务队列是否已满?放入队列等待最大线程数是否已满?创建非核心线程执行执行拒绝策略6.4 四种拒绝策略AbortPolicy直接抛出 RejectedExecutionException默认。CallerRunsPolicy由提交任务的线程自己执行。DiscardPolicy直接丢弃任务。DiscardOldestPolicy丢弃队列中最旧的任务再提交新任务。6.5 为什么不推荐 Executors 工厂方法newFixedThreadPool和newSingleThreadExecutor队列为无界LinkedBlockingQueue可能堆积大量任务导致 OOM。newCachedThreadPool最大线程数为Integer.MAX_VALUE可能创建过多线程导致 OOM。推荐手动创建ThreadPoolExecutor明确参数。7. 并发工具类7.1 CountDownLatch允许一个或多个线程等待其它线程完成操作。CountDownLatchlatchnewCountDownLatch(3);// 每个工作线程完成后调用 latch.countDown();// 主线程调用 latch.await() 等待所有任务完成7.2 CyclicBarrier让一组线程互相等待到达某个屏障点后再同时继续执行可循环使用。CyclicBarrierbarriernewCyclicBarrier(3);// 每个线程到达后调用 barrier.await()7.3 Semaphore控制同时访问某个资源的线程数量常用于限流。SemaphoresemaphorenewSemaphore(3);semaphore.acquire();try{// 访问共享资源}finally{semaphore.release();}7.4 ConcurrentHashMapJDK 1.7分段锁Segment。JDK 1.8CAS synchronized锁粒度细化到桶Node并发度更高。8. ThreadLocal8.1 作用提供线程局部变量每个线程拥有独立的变量副本互不干扰。ThreadLocalIntegerthreadLocalnewThreadLocal();threadLocal.set(100);IntegervaluethreadLocal.get();8.2 底层原理每个Thread内部有一个ThreadLocalMap以ThreadLocal为 key变量值为 value。8.3 内存泄漏问题ThreadLocalMap的 key 是弱引用value 是强引用。当ThreadLocal被回收后key 变为 null但 value 仍被引用导致内存泄漏。解决使用完调用remove()方法清理。9. 常见面试题汇总9.1 线程与进程的区别是什么进程是资源分配的基本单位线程是 CPU 调度的基本单位进程间相互独立线程共享进程内存。9.2 synchronized 和 volatile 的区别volatile 保证可见性和有序性不保证原子性synchronized 三者都保证。volatile 不能修饰方法synchronized 可以。volatile 性能开销更小。9.3 线程池的核心线程数如何设置CPU 密集型CPU 核数 1。IO 密集型CPU 核数 * 2或CPU 核数 / (1 - 阻塞系数)。9.4 什么是死锁如何避免多个线程互相持有对方需要的锁且不释放。避免方式按固定顺序加锁、使用超时锁tryLock、减少锁的持有时间。9.5 wait() 和 sleep() 的区别wait 释放锁sleep 不释放。wait 是 Object 方法sleep 是 Thread 静态方法。wait 需要被唤醒sleep 到时间自动恢复。9.6 wait() vs sleep() vs yield() vs join() 对比方法所属类是否释放锁唤醒方式使用场景wait()Object释放锁需notify()/notifyAll()或wait(timeout)超时唤醒线程间协作、生产者-消费者模式sleep()Thread静态不释放锁时间到自动恢复模拟耗时、控制执行节奏yield()Thread静态不释放锁让出 CPU由调度器重新调度提示调度器让出执行权多用于调试或公平调度join()Thread实例不释放锁被 join 的线程执行完毕后自动恢复等待子线程执行完成后再继续使用注意事项wait()必须在synchronized代码块或方法中调用否则抛出IllegalMonitorStateException调用后释放锁并进入 WAITING 状态需配合notify()/notifyAll()使用且建议在循环中判断等待条件避免虚假唤醒。sleep()不释放锁持有锁期间其它线程无法进入临界区sleep()到时间自动恢复但可能被interrupt()中断并抛出InterruptedException需妥善处理。yield()只是向调度器发出「让出 CPU」的提示是否让出由 JVM 和操作系统决定不保证一定生效通常用于调试或低优先级任务生产环境慎用。join()调用线程会进入 WAITING 状态等待目标线程结束join(timeout)可设置最长等待时间避免无限期阻塞若目标线程被中断会抛出InterruptedException。
返回列表