
Java高级深入面试题合集(JVM/线程/并发)
文章平均质量分 71
主要面对Java高级深入面试题
EthanMilk
加油
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
谈谈你对AQS的理解,AQS如何实现可重⼊锁?
在AQS中,维护了⼀个信号量state和⼀个线程组成的双向链表队列。其中,这个线程队列,就是⽤来给线程排队的,⽽state就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的。在不同的场景下,有不⽤的意义。在可重⼊锁这个场景下,state就⽤来表示加锁的次数。0标识⽆锁,每加⼀次锁,state就加1。AQS是⼀个JAVA线程同步的框架。:新线程可以插队竞争锁(默认模式,吞吐量更高)。),用于更灵活的线程等待/唤醒。)的基石,用于构建锁和同步器的。:锁被占用,数值表示重入次数。记录当前持有锁的线程)。原创 2025-04-07 01:28:55 · 430 阅读 · 0 评论 -
Sychronized和ReentrantLock的区别
sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态。sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁。sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁。sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁。锁的状态(偏向锁、轻量级锁、重量级锁)由 JVM 自动管理。默认是非公平锁(不保证线程获取锁的顺序)。原创 2025-04-07 01:26:55 · 501 阅读 · 0 评论 -
Sychronized的偏向锁、轻量级锁、重量级锁
⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒 这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了.当其他线程尝试获取锁时,偏向锁会撤销(Revoke),升级为轻量级锁。原创 2025-04-07 01:24:28 · 1039 阅读 · 0 评论 -
CountDownLatch和Semaphore的区别和底层原理
CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,⼀个线程调⽤CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中 排队的线程依次唤醒。,通过CAS增加许可证数,并唤醒阻塞线程。,如果无可用许可证则阻塞。原创 2025-04-07 01:22:17 · 641 阅读 · 0 评论 -
ReentrantLock中tryLock()和lock()⽅法的区别
tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回true,没有加到则返回false。lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值。必须确保锁被获取的场景(如数据库事务、支付扣减)。的语义,但更灵活(可中断、可设置公平性)。:减少线程阻塞时间(如缓存更新、心跳检测)。:非关键任务、死锁预防(如分布式锁)。:如果锁被占用,可以立即回退或重试。如果锁被其他线程持有,当前线程会。:锁被占用时立即返回或超时放弃。:如果锁可用,则获取锁并返回。原创 2025-04-07 01:19:53 · 872 阅读 · 0 评论 -
ReentrantLock中的公平锁和⾮公平锁的底层实现
⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,它们的区别在于:线程在使⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队, 则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。原创 2025-04-07 01:14:39 · 210 阅读 · 0 评论 -
线程池中线程复⽤原理
在线程池中,同⼀个线程可以从阻塞队列中不断获取新任务来执⾏,其核⼼原理在于线程池对 Thread 进⾏了封装,并不是每次执⾏任务都会调⽤ Thread.start() 来创建新线程,⽽是让每个线程去执⾏⼀个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执⾏,如果有则直接执⾏,也就是调⽤ 任务中的 run ⽅法,将 run ⽅法当成⼀个普通的⽅法执⾏,通过这种⽅式只使⽤固定的线程就将所有任务的 run ⽅法串联起来。线程处理完一个请求后,立即从队列获取下一个请求,避免为每个请求创建新线程。原创 2025-04-07 01:14:55 · 797 阅读 · 0 评论 -
线程池中阻塞队列的作⽤?为什么是先添加列队⽽不是先创建最⼤线程?
就好⽐⼀个企业⾥⾯有10个(core)正式⼯的名额,最多招10个正式⼯,要是任务超过正式⼯⼈数(task > core)的情况下,⼯⼚领导(线程池)不是⾸先扩招⼯⼈,还是这10⼈,但是任务可以稍微积压⼀下,即先放到队列去(代价低)。10个正式⼯慢慢⼲,迟早会⼲完的,要是任务还在继续增加,超过正式⼯的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时⼯)要是正式⼯加上外包还 是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。若已达最大线程数,触发拒绝策略(如丢弃任务或抛出异常)。原创 2025-04-06 04:41:12 · 802 阅读 · 0 评论 -
为什么用线程池?解释下线程池参数?
频繁创建/销毁线程开销大(涉及操作系统资源分配、GC压力)。线程池复用已存在的线程,减少线程创建销毁的开销。原创 2025-04-06 04:37:07 · 706 阅读 · 0 评论 -
如何理解volatile关键字
当线程2更改了stop变量的值之后,但是还没来得及写⼊主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因 此还会⼀直循环下去。write⽅法⾥的1和2做了重排序,线程1先对flag赋值为true,随后执⾏到线程2,ret直接计算出结果,再 到线程1,这时候a才赋值为2,很明显迟了⼀步。使⽤volatile关键字的话,当线程2进⾏修改时,会导致线程1的⼯作内存中缓存变量stop的缓存⾏⽆效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存⾏⽆效);原创 2025-04-06 04:32:24 · 114 阅读 · 0 评论 -
Java死锁如何避免?
这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3 个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。死锁(Deadlock)是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的情况。:多个线程形成环形等待链(A 等 B,B 等 C,C 等 A)。⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺。:如果拿不到锁,就释放已持有的锁,避免无限等待。:线程持有资源的同时,等待其他资源。:资源一次只能被一个线程占用。原创 2025-04-06 04:31:21 · 943 阅读 · 0 评论 -
并发的三大特性
那程序中原⼦性指的是最⼩的操作单元,⽐如⾃增操作,它本身其实并不是原⼦性操作,分了3步的, 包括读取变量的原始值、进⾏加1操作、写⼊⼯作内存。如果线程2改变了stop的值,线程1⼀定会停⽌吗?当线程2更改了stop变量的值之后,但是还没来得及写⼊主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因 此还会⼀直循环下去。若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2⼜使⽤了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可⻅性问题。原创 2025-04-06 04:26:30 · 174 阅读 · 0 评论 -
并发、并行、串行之间的区别
如果任务有依赖(如 B 需要 A 的结果),强行并行反而更慢。交替执行(如:A 执行 10ms → 切换 B → 切换 C)。I/O 密集型任务(如 Web 服务器处理多请求)。简单脚本、依赖性强的任务(如:先登录才能下单)。CPU 密集型任务(如视频编码、大规模计算)。:I/O 密集型(如网络请求、文件读写)。:CPU 密集型(如矩阵运算、机器学习)。的同时(如单核 CPU 多任务切换)。真正同时执行(需多核 CPU)。无任务切换,完全利用多核资源。的同时(需多核 CPU)。:任务简单、无性能要求。原创 2025-04-06 04:24:14 · 1282 阅读 · 0 评论 -
ThreadLocal的底层原理
如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象。原创 2025-04-06 04:20:09 · 848 阅读 · 0 评论 -
守护线程的作用是什么?
反之,如果⼀个正在执⾏某个操作的线程必须要正 确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,⽽是⽤户线程。举例, GC垃圾回收线程:就是⼀个经典的守护线程,当我们的程序中不再有任何运⾏的Thread,程序就不会再产⽣垃圾,垃圾回收器也就⽆事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会⾃动离开。Java⾃带的多线程框架,⽐如ExecutorService,会将守护线程转换为⽤户线程,所以如果要使⽤后台线程就不能⽤Java的线程池。,守护线程被终止时可能来不及清理资源。原创 2025-04-06 04:14:46 · 340 阅读 · 0 评论 -
对守护线程的理解
哪天其他线程结束了,没有要执⾏的了,程序就结束了,理都没理守护线程,就把它中断了;:只要公司还有人(普通线程)在工作,保洁阿姨就会继续打扫;但如果所有员工都下班了(主线程结束),保洁阿姨也会立刻停止工作,不管她手头的活干没干完。注意: 由于守护线程的终⽌是⾃身⽆法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;守护线程:为所有⾮守护线程提供服务的线程;→ 保洁阿姨立刻停止备份(即使还没到1小时),店铺关门(JVM 退出)。:主线程结束后,守护线程被强行终止,不会继续打印。(否则可能丢失数据)。原创 2025-04-06 04:12:22 · 348 阅读 · 0 评论 -
Thread和Runable的区别
Thread和Runnable的实质是继承关系,没有可⽐性。⽆论使⽤Runnable还是Thread,都会new Thread,然后执⾏run⽅法。⽤法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执⾏⼀个任务,那就实现runnable。原因是:MyThread创建了两个实例,⾃然会卖出两倍,属于⽤法错误。原创 2025-04-06 04:07:53 · 160 阅读 · 0 评论 -
对线程安全的理解
在Java中,堆是Java虚拟机所管理的内存中最⼤的⼀块,是所有线程共享的⼀块内存区域,在虚拟机启动时创建。不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问,当多个线程访问⼀个对象时, 如果不⽤进⾏额外的同步控制或其他的协调操作,调⽤这个对象的⾏为都可以获得正确的结果,我们就 说这个对象是线程安全的。栈在线程开始的时候初始化,每个线程的栈 互相独⽴,因此,栈是线程安全的。为了保证安全,每个进程只能访问分配给⾃⼰ 的内存空间,⽽不能访问别的进程的,这是由操作系统保障的。原创 2025-04-06 04:07:01 · 151 阅读 · 0 评论 -
sleep()、wait()、join()、yield()之间的的区别2
等待池:当我们调⽤wait()⽅法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。锁池:所有需要竞争同步锁的线程都会放在锁池当中,⽐如当前对象的锁已经被其中⼀个线程得到,则 其他线程需要在这个锁池进⾏等待,当前⾯的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线 程得到后会进⼊就绪队列进⾏等待cpu资源分配。yield()执⾏后线程直接进⼊就绪状态,⻢上释放了cpu的执⾏权,但是依然保留了cpu的执⾏资格,所以有可能cpu下次进⾏线程调度还会让这个线程获取到执⾏权继续执⾏。原创 2025-04-06 04:06:14 · 274 阅读 · 0 评论 -
深入解析线程控制机制:锁池、等待池与线程方法对比
采用CLH队列(Craig, Landin, Hagersten lock queue)采用公平锁时按FIFO顺序获取非公平锁时可能发生线程插队。原创 2025-04-06 04:04:32 · 324 阅读 · 0 评论 -
sleep()、wait()、join()、yield()之间的的区别
不释放任何锁(包括synchronized和Lock)注意join()本身是synchronized方法。忽略InterruptedException处理。线程间协作 → wait()/notify()避免CPU占用过高 → yield()(慎用)混淆sleep()和wait()的锁行为。调用后释放锁并进入WAITING状态。过度依赖yield()进行线程调度。需要定时暂停 → sleep()控制执行顺序 → join()适用于调试或极端性能优化场景。在非同步块中调用wait()被唤醒后需要重新竞争锁。原创 2025-04-06 03:56:35 · 266 阅读 · 0 评论