线程面试题及答案_第1页
线程面试题及答案_第2页
线程面试题及答案_第3页
线程面试题及答案_第4页
线程面试题及答案_第5页
已阅读5页,还剩18页未读 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

线程面试题及答案线程与进程的核心区别是什么?在Java中如何体现?线程是操作系统能够进行运算调度的最小单位,进程是资源分配的基本单位。进程拥有独立的内存空间、文件句柄等资源,而同一进程内的线程共享进程资源(如堆、方法区),仅拥有独立的栈、程序计数器和局部变量表。Java中,一个JVM实例对应一个进程,进程内的多个线程通过Thread类或Runnable接口创建,共享JVM的堆内存,但各自的调用栈独立。例如,主线程(main)和用户创建的子线程均运行在同一个JVM进程中,共同访问堆中的对象,但各自的局部变量存储在独立的栈帧中。Java中创建线程的三种方式及其优缺点?Java创建线程的方式有三种:1.继承Thread类,重写run()方法:通过new自定义Thread子类并调用start()启动。优点是代码简单,直接操作线程对象;缺点是受单继承限制(Java类只能继承一个父类),无法复用其他类的功能。2.实现Runnable接口,重写run()方法:将Runnable实例传入Thread构造器,调用start()启动。优点是解耦任务(Runnable)与线程(Thread),支持多继承(类可同时实现多个接口);缺点是无返回值,无法直接获取任务执行结果。3.实现Callable接口,配合Future或ExecutorService:Callable的call()方法有返回值并可抛出异常,需通过ExecutorService.submit()提交,返回Future对象获取结果。优点是支持异步计算和结果获取;缺点是使用较复杂,需结合线程池或FutureTask。线程的生命周期包含哪些状态?状态转换的触发条件是什么?Java线程的生命周期由Thread.State枚举定义,包含6种状态:NEW(新建):线程对象创建但未调用start()。RUNNABLE(可运行):线程已启动(调用start()),处于JVM的运行状态,可能在等待CPU时间片(READY)或正在执行(RUNNING)。BLOCKED(阻塞):线程尝试获取synchronized同步锁失败,进入锁的等待队列。WAITING(无限等待):调用wait()(无超时)、join()(无超时)或LockSupport.park()后,线程释放锁并进入等待状态,需其他线程调用notify()/notifyAll()或unpark()唤醒。TIMED_WAITING(定时等待):调用sleep(long)、wait(long)、join(long)或parkNanos(long)后,线程在指定时间后自动唤醒。TERMINATED(终止):run()或call()执行完毕,或因异常终止。状态转换示例:NEW→RUNNABLE(start());RUNNABLE→BLOCKED(获取锁失败);RUNNABLE→TIMED_WAITING(sleep(1000));WAITING→RUNNABLE(其他线程notify());RUNNABLE→TERMINATED(任务执行完成)。synchronized关键字的底层实现原理是什么?锁升级过程如何?synchronized的底层依赖JVM的Monitor(监视器)对象。每个Java对象(包括类的Class对象)关联一个Monitor,Monitor的Enter和Exit操作通过字节码指令monitorenter和monitorexit实现。线程执行monitorenter时尝试获取Monitor的所有权:若未被占用则获取成功(锁计数+1);若已被当前线程占用则重入(计数+1);否则阻塞等待。锁升级是JVM为优化synchronized性能引入的机制,过程如下:1.无锁:对象头MarkWord标记为无锁状态,存储哈希码、GC分代年龄。2.偏向锁:当线程首次获取锁时,MarkWord存储该线程ID(偏向模式),后续同一线程再次获取锁无需CAS操作,仅检查线程ID是否匹配。若其他线程尝试获取锁,偏向锁撤销,升级为轻量级锁。3.轻量级锁:原持有线程将MarkWord复制到栈的锁记录(LockRecord),并通过CAS将对象头指向锁记录。其他线程尝试获取锁时,若发现对象头指向当前线程的锁记录(自旋),则自旋等待(避免阻塞);若自旋失败(超过次数或CPU忙),轻量级锁升级为重量级锁。4.重量级锁:线程进入Monitor的等待队列(EntrySet),状态转为BLOCKED,需等待前一线程释放锁并唤醒。ReentrantLock与synchronized的核心差异有哪些?适用场景如何选择?ReentrantLock(可重入锁)是Java并发包(java.util.concurrent.locks)提供的显式锁,与synchronized的差异主要体现在:1.锁获取方式:synchronized是隐式锁(自动释放),通过JVM字节码实现;ReentrantLock需手动调用lock()获取、unlock()释放(通常在finally块中)。2.锁特性:ReentrantLock支持公平锁(按等待队列顺序分配锁)和非公平锁(默认,允许插队提升性能);synchronized仅支持非公平锁。3.可中断性:ReentrantLock的lockInterruptibly()方法允许线程在等待锁时响应中断;synchronized阻塞时无法被中断。4.条件变量:ReentrantLock通过newCondition()获取多个Condition对象,支持精准唤醒(如生产者-消费者模型中唤醒特定线程);synchronized仅能通过wait()/notify()/notifyAll()实现单一条件队列。选择建议:简单同步场景(如方法或代码块加锁)优先用synchronized(代码简洁,JVM优化后性能接近);需要公平锁、可中断锁或多条件变量时选择ReentrantLock(如高并发下的资源分配)。线程池的核心参数有哪些?各参数的具体作用是什么?线程池(ThreadPoolExecutor)的核心参数通过构造函数定义,共7个:1.corePoolSize(核心线程数):线程池长期保留的最小线程数(即使空闲也不会销毁,除非设置allowCoreThreadTimeOut为true)。2.maximumPoolSize(最大线程数):线程池允许创建的最大线程数(核心线程+临时线程)。3.keepAliveTime(空闲存活时间):临时线程(超过核心线程数的部分)在无任务时的存活时间,超时后销毁。4.unit(时间单位):keepAliveTime的时间单位(如TimeUnit.SECONDS)。5.workQueue(工作队列):存储待执行任务的阻塞队列,常用类型有ArrayBlockingQueue(有界)、LinkedBlockingQueue(无界/有界)、SynchronousQueue(无存储,直接提交)、PriorityBlockingQueue(优先级队列)。6.threadFactory(线程工厂):创建线程的工厂类,可自定义线程名称、优先级等(默认DefaultThreadFactory)。7.handler(拒绝策略):当工作队列满且线程数达最大值时,对新任务的处理策略。线程池的工作流程是怎样的?当任务提交时,线程池如何处理?线程池处理任务的流程遵循“核心线程→工作队列→临时线程→拒绝策略”的顺序:1.提交任务时,若当前线程数小于corePoolSize,立即创建新线程(核心线程)执行任务(即使有空闲线程)。2.若核心线程已满(当前线程数≥corePoolSize),任务被加入workQueue等待执行。3.若workQueue已满且当前线程数小于maximumPoolSize,创建临时线程执行任务。4.若workQueue已满且当前线程数≥maximumPoolSize,触发拒绝策略处理新任务。例如:corePoolSize=3,maximumPoolSize=5,workQueue=ArrayBlockingQueue(容量2)。当提交6个任务时:前3个任务由核心线程执行;第4、5个任务入队;第6个任务因队列已满且当前线程数(3)<max(5),创建第4个临时线程执行;若继续提交第7个任务,此时线程数=5(核心3+临时2),队列已满,触发拒绝策略。常见的线程池拒绝策略有哪些?各自的适用场景是什么?ThreadPoolExecutor提供4种内置拒绝策略:1.AbortPolicy(默认):直接抛出RejectedExecutionException异常。适用于关键任务,不允许丢弃(如金融交易),需上层捕获异常并处理(如重试)。2.CallerRunsPolicy:由调用者线程(提交任务的线程)直接执行任务。适用于流量控制场景(如主线程提交任务,若线程池饱和则主线程参与执行,降低提交速度)。3.DiscardPolicy:静默丢弃新任务,无任何提示。适用于允许丢失非关键任务(如日志记录)。4.DiscardOldestPolicy:丢弃工作队列中最旧的任务(队首任务),并尝试重新提交当前任务。适用于任务有时间敏感性(如实时统计,旧数据价值低)。如何合理配置线程池的核心线程数和最大线程数?需要考虑哪些因素?线程池的配置需结合任务类型(CPU密集型/IO密集型)和系统资源(CPU核心数、内存):CPU密集型任务(如复杂计算):线程数应接近或略大于CPU核心数(公式:corePoolSize=CPU核心数+1)。因CPU密集型任务很少等待IO,过多线程会增加上下文切换开销。IO密集型任务(如数据库查询、文件读写):线程数可设为CPU核心数×2或更高(公式:corePoolSize=CPU核心数×(1+线程等待时间/线程运行时间))。因IO操作时线程空闲,需更多线程保持CPU利用率。其他考虑因素:任务耗时:长耗时任务需减少线程数(避免资源耗尽),短耗时任务可增加线程数。内存限制:每个线程的栈空间(默认1MB)会占用内存,最大线程数需避免OOM(内存溢出)。响应时间:高并发场景下,增大核心线程数和队列容量可减少任务等待时间,但需平衡资源利用率。CountDownLatch、CyclicBarrier、Semaphore的核心区别是什么?举例说明使用场景。三者均为并发工具类(java.util.concurrent),但功能不同:CountDownLatch(倒计数器):初始化时设置计数(count),线程调用countDown()递减计数,等待线程通过await()阻塞,直到计数为0。特点是一次性(计数归零后无法重置)。场景:主线程等待所有子线程完成初始化(如启动多个线程加载配置,主线程等待所有配置加载完毕后继续执行)。CyclicBarrier(循环栅栏):初始化时设置parties(需等待的线程数),每个线程调用await()后阻塞,直到所有parties个线程都调用await(),栅栏打开(触发Runnable任务,可选),并可重复使用(计数自动重置)。场景:多阶段任务同步(如多个线程处理同一数据的不同部分,完成第一阶段后共同进入第二阶段)。Semaphore(信号量):通过acquire()获取许可(permits),release()释放许可,控制同时执行的线程数(许可数即最大并发量)。场景:限制资源访问量(如数据库连接池最多允许10个线程同时连接,超过则等待)。死锁发生的四个必要条件是什么?如何检测和避免死锁?死锁的四个必要条件:1.互斥条件:资源同一时间只能被一个线程占用(如synchronized锁)。2.持有并等待:线程已持有至少一个资源,同时请求其他线程持有的资源,且不释放已持有资源。3.不可抢占:资源只能由持有者主动释放,无法被其他线程强行抢占。4.循环等待:多个线程形成环形等待链(线程A等线程B的资源,线程B等线程A的资源)。检测方法:使用JDK工具(如jstack)打印线程堆栈,若发现线程状态为BLOCKED且等待的锁被其他线程持有,形成循环依赖,即可判定死锁。代码中通过ThreadMXBean检测(ThreadMXBean.findDeadlockedThreads()返回死锁线程ID)。避免策略:破坏“持有并等待”:一次性获取所有需要的资源(如按固定顺序加锁)。破坏“不可抢占”:使用可中断锁(如ReentrantLock的lockInterruptibly())或定时锁(tryLock(longtimeout,TimeUnitunit)),超时后释放已持有资源。破坏“循环等待”:统一加锁顺序(如所有线程先获取锁A,再获取锁B,避免A→B和B→A的交叉顺序)。什么是线程安全?哪些操作可能导致线程不安全?如何解决?线程安全指多个线程并发访问时,对象的状态始终保持一致(符合预期)。线程不安全的根源是共享可变状态的访问未正确同步。常见线程不安全操作:1.非原子操作:如i++(实际为“读取-修改-写入”三步,中间可能被其他线程打断)。2.共享变量未同步:多个线程修改同一变量,未通过锁或volatile保证可见性。3.指令重排:JVM为优化性能可能重排指令顺序(如单例模式的双重检查锁定未用volatile,导致其他线程获取到未初始化的对象)。解决方案:使用synchronized或Lock同步代码块,保证原子性和可见性。使用原子类(如AtomicInteger),通过CAS(比较并交换)保证操作的原子性。使用volatile修饰状态变量,保证可见性和禁止指令重排(适用于布尔标志位)。避免共享状态(如使用ThreadLocal为每个线程维护独立副本)。volatile关键字的作用是什么?与synchronized的区别是什么?volatile的作用:1.保证可见性:被volatile修饰的变量,修改后立即刷新到主内存,其他线程读取时直接从主内存获取,避免使用本地缓存的旧值(基于JVM的内存屏障实现)。2.禁止指令重排:通过插入内存屏障(LoadStore、StoreLoad等),防止编译器或CPU对指令顺序的重排(如单例模式中防止“分配内存→赋值引用→初始化对象”的重排)。与synchronized的区别:范围:volatile修饰变量,synchronized修饰方法或代码块。原子性:volatile不保证原子性(如volatileinti;i++仍非原子),synchronized保证操作的原子性。锁机制:synchronized涉及锁的获取与释放(可能引发线程阻塞),volatile无锁,性能更轻量。可见性:两者均保证可见性,但实现方式不同(synchronized通过锁的释放-获取保证,volatile通过内存屏障)。ThreadLocal的实现原理是什么?可能引发的内存泄漏问题如何解决?ThreadLocal为每个线程提供独立的变量副本,避免共享状态。其核心是Thread类中的threadLocals字段(类型为ThreadLocal.ThreadLocalMap),每个ThreadLocal对象作为Map的key(弱引用),对应的值为线程的变量副本。内存泄漏原因:ThreadLocalMap的Entry(键值对)中,key是弱引用(WeakReference<ThreadLocal>),当ThreadLocal对象无强引用时会被GC回收(key变为null),但value是强引用(指向线程的变量副本),若线程未终止(如线程池中的长生命周期线程),value无法被回收,导致内存泄漏。解决方法:手动调用remove():在不需要ThreadLocal变量时(如线程任务结束前),调用ThreadLocal的remove()方法删除Entry。限制ThreadLocal的作用域:避免在线程池中使用全局ThreadLocal,改为局部变量或任务结束后清理。使用静态ThreadLocal:若ThreadLocal为静态变量(与类生命周期绑定),key不会被GC回收,需注意及时清理。Callable与Runnable的区别是什么?如何获取异步任务的执行结果?Callable与Runnable的核心区别:Callable的call()方法有返回值(类型V),可抛出受检异常;Runnable的run()方法无返回值,不能抛出受检异常。Callable需配合ExecutorService或FutureTask使用,Runnable可直接作为Thread的构造参数。获取异步结果的方式:1.通过ExecutorService.submit(Callable<T>):返回Future<T>对象,调用Future.get()阻塞获取结果(或get(longtimeout,TimeUnitunit)超时获取)。2.通过FutureTask:将Callable包装为FutureTask(实现RunnableFuture接口),传入Thread构造器启动线程,再调用FutureTask.get()获取结果。示例:ExecutorServiceexecutor=Executors.newSingleThreadExecutor();Future<Integer>future=executor.submit(()->{Thread.sleep(1000);return42;});Integerresult=future.get();//阻塞直到任务完成线程的中断机制是怎样的?interrupt()、isInterrupted()、interrupted()有何区别?线程的中断是一种协作机制(非强制终止),通过设置中断标志位通知线程“应终止”,由线程自行决定是否响应。关键方法:interrupt():调用线程的interrupt()方法,设置该线程的中断标志位(若线程在wait()/sleep()/join()中,会抛出InterruptedException并清除标志位)。isInterrupted():实例方法,返回线程的中断标志位状态(不清除标志)。interrupted():静态方法,返回当前线程的中断标志位状态,并清除标志(调用后标志位重置为false)。正确响应中断的方式:在循环任务中检查中断标志(通过Thread.currentThread().isInterrupted()),或捕获InterruptedException后退出循环。例如:while(!Thread.currentThread().isInterrupted()){try{//执行任务Thread.sleep(100);}catch(InterruptedExceptione){//被中断,重置标志并退出Thread.currentThread().interrupt();break;}}原子类(如AtomicInteger)是如何保证操作原子性的?CAS机制的优缺点是什么?原子类(如AtomicInteger)通过CAS(Compare-And-Swap,比较并交换)保证操作的原子性。CAS是一种无锁算法,依赖Unsafe类的native方法(如compareAndSwapInt)实现,包含三个参数:内存地址(V)、预期旧值(A)、新值(B)。若V处的值等于A,则将V更新为B;否则不操作。整个过程由CPU的原子指令(如cmpxchg)保证原子性。CAS的优点:无锁:避免线程阻塞和上下文切换,性能优于synchronized(低竞争场景)。细粒度:支持单个变量的原子操作(如自增、比较交换)。CAS的缺点:ABA问题:变量值从A→B→A,CAS认为未变化(实际已修改)。可通过AtomicStampedReference(带版本号)或AtomicMarkableReference(带标记)解决。自旋消耗:高竞争场景下,线程多次CAS失败会持续自旋,浪费CPU资源。只能保证单个变量的原子性:多个变量需原子操作时,仍需锁(如synchronized或ReentrantLock)。什么是线程的上下文切换?哪些操作会触发上下文切换?如何减少上下文切换?线程的上下文切换指CPU从一个线程切换到另一个线程执行时,保存当前线程的状态(PC计数器、寄存器值、栈指针等)并加载另一个线程状态的过程。触发上下文切换的常见操作:时间片耗尽:操作系统为每个线程分配时间片,超时后切换。锁竞争:线程获取synchronized锁失败,进入阻塞状态,触发切换。IO阻塞:线程执行IO操作(如读取文件)时,因等待资源进入阻塞,CPU切换其他线程。主动放弃:线程调用yield()(提示CPU切换)或sleep()(主动休眠)。中断处理:外部中断(如用户输入)触发CPU切换到中断处理线程。减少上下文切换的方法:减少锁竞争:使用无锁数据结构(如ConcurrentHashMap)或缩小同步代码块范围。优化线程数:避免创建过多线程(CPU密集型线程数≈核心数,IO密集型合理估算)。使用CAS替代锁:低竞争场景下,原子类的CAS操作减少阻塞。批量处理任务:将小任务合并,减少线程频繁创建和销毁(如线程池复用线程)。

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论