27道多线程核心面试题(附答案),小白必看

3. 通过CAS操作竞争锁,如果竞争成功,则将MarkWord中线程ID设置为当前线程ID,然后执行‘V’;竞争失败,则执行‘IV’

4. CAS获取偏向锁失败表示有竞争。当达到safepoint时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块

5. 执行同步代码

轻量级锁是相对于重量级锁需要阻塞/唤醒涉及上下文切换而言,主要针对多个线程在不同时间请求同一把锁的场景。轻量级锁获取过程:

  • 进行加锁操作时,jvm会判断是否已经是重量级锁,如果不是,则会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中

  • 复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘III’,否则执行‘IV’

  • 更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此对象处于轻量级锁状态

  • 更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执行‘V’,否则执行‘VI’

  • 表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word) 为null,并指向Mark Word的锁对象,起到一个重入计数器的作用

  • 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,则升级为重量级锁

当有多个锁竞争轻量级锁则会升级为重量级锁,重量级锁正常会进入一个cxq的队列,在调用wait方法之后,则会进入一个waitSet的队列park等待,而当调用notify方法唤醒之后,则有可能进入EntryList。重量级锁加锁过程:

  • 分配一个ObjectMonitor对象,把Mark Word锁标志置为‘10’,然后Mark Word存储指向ObjectMonitor对象的指针。ObjectMonitor对象有两个队列和一个指针,每个需要获取锁的线程都包装成ObjectWaiter对象

  • 多个线程同时执行同一段同步代码时,ObjectWaiter先进入EntryList队列,当某个线程获取到对象的monitor以后进入Owner区域,并把monitor中的owner变量设置为当前线程同时monitor中的计数器count+1;

10.简单描述一下ABA问题?

===============

1.有两个线程同时去修改一个变量的值,比如线程1、线程2,都更新变量值,将变量值从A更新成B。

2.首先线程1、获取到CPU的时间片,线程2由于某些原因发生阻塞进行等待,此时线程1进行比较更新(CompareAndSwap),成功将变量的值从A更新成B。

3.更新完毕之后,恰好又有线程3进来想要把变量的值从B更新成A,线程3进行比较更新,成功将变量的值从B更新成A。 4. 线程2获取到CPU的时间片,然后进行比较更新,发现值是预期的A,然后又更新成了B。但是线程1并不知道,该值已经有了A->B->A这个过程,这也就是我们常说的ABA问题。

4.可以通过加版本号或者加时间戳解决,或者保证单向递增或者递减就不会存在此类问题。

11.实现一下DCL?

===========

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

12.实现一个阻塞队列(用Condition写生产者与消费者就)?

=================================

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

13.实现多个线程顺序打印abc?

=================

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

14.服务器CPU数量及线程池线程数量的关系?

=======================

首先确认业务是CPU密集型还是IO密集型的,

如果是CPU密集型的,那么就应该尽量少的线程数量,一般为CPU的核数+1;

如果是IO密集型:所以可多分配一点 cpu核数*2 也可以使用公式:CPU 核数 / (1 - 阻塞系数);其中阻塞系数 在 0.8 ~ 0.9 之间。

15.多线程之间是如何通信的?

===============

1、通过共享变量,变量需要volatile 修饰

2、使用wait()和notifyAll()方法,但是由于需要使用同一把锁,所以必须通知线程释放锁,被通知线程才能获取到锁,这样导致通知不及时。

3、使用CountDownLatch实现,通知线程到指定条件,调用countDownLatch.countDown(),被通知线程进行countDownLatch.await()。

4、使用Condition的await()和signalAll()方法。

16.synchronized关键字加在静态方法和实例方法的区别?

=================================

修饰静态方法,是对类进行加锁,如果该类中有methodA 和methodB都是被synchronized修饰的静态方法,此时有两个线程T1、T2分别调用methodA()和methodB(),则T2会阻塞等待直到T1执行完成之后才能执行。

修饰实例方法时,是对实例进行加锁,锁的是实例对象的对象头,如果调用同一个对象的两个不同的被synchronized修饰的实例方法时,看到的效果和上面的一样,如果调用不同对象的两个不同的被synchronized修饰的实例方法时,则不会阻塞。

17.countdownlatch的用法?

=====================

两种用法:

1、让主线程await,业务线程进行业务处理,处理完成时调用countdownLatch.countDown(),CountDownLatch实例化的时候需要根据业务去选择CountDownLatch的count;

2、让业务线程await,主线程处理完数据之后进行countdownLatch.countDown(),此时业务线程被唤醒,然后去主线程拿数据,或者执行自己的业务逻辑。

18.线程池问题:

=========

(1)Executor提供了几种线程池

1、newCachedThreadPool()(工作队列使用的是 SynchronousQueue)

创建一个线程池,如果线程池中的线程数量过大,它可以有效地回收多余的线程,如果线程数不足,那么它可 以创建新的线程。

不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处 理却是我们无法控制的。

优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并 不会重新创建新的线程,提高了线程的复用率。

作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确 定,是根据实际情况动态调整的。

2、newFixedThreadPool()(工作队列使用的是 LinkedBlockingQueue)

这种方式可以指定线程池中的线程数。如果满了后又来了新任务,此时只能排队等待。

优点:newFixedThreadPool 的线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务 器达到最大的使用率,同时又可以保证即使流量突然增大也不会占用服务器过多的资源。

作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也 不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数

3、newScheduledThreadPool()

该线程池支持定时,以及周期性的任务执行,我们可以延迟任务的执行时间,也可以设置一个周期性的时间让 任务重复执行。该线程池中有以下两种延迟的方法。

scheduleAtFixedRate 不同的地方是任务的执行时间,如果间隔时间大于任务的执行时间,任务不受执行 时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,至此间隔时间就会被 打乱。

scheduleWithFixedDelay 的间隔时间不会受任务执行时间长短的影响。

作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。

4、newSingleThreadExecutor()

这是一个单线程池,至始至终都由一个线程来执行。

作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务 队列中,等待这一个线程空闲,当这个线程空闲了再按 FIFO 方式顺序执行任务队列中的任务。

5、

newSingleThreadScheduledExecutor()

只有一个线程,用来调度任务在指定时间执行。

作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线 程池大小为 1,而上面的可以指定线程池的大小。

(2)线程池的参数

int corePoolSize,//线程池核心线程大小

int maximumPoolSize,//线程池最大线程数量

long keepAliveTime,//空闲线程存活时间

TimeUnit unit,//空闲线程存活时间单位,一共有七种静态属性(TimeUnit.DAYS天,TimeUnit.HOURS 小时,TimeUnit.MINUTES分钟,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫 秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS纳秒)

BlockingQueue workQueue,//工作队列

ThreadFactory threadFactory,//线程工厂,主要用来创建线程(默认的工厂方法是:

Executors.defaultThreadFactory()对线程进行安全检查并命名)

RejectedExecutionHandler handler//拒绝策略(默认是:

ThreadPoolExecutor.AbortPolicy不 执行并抛出异常)

(3)拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进 来,就会执行拒绝策略。

jdk中提供了4种拒绝策略:

ThreadPoolExecutor.CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的 run 方法,除非线程池已经 shutdown,则直接抛弃任 务。

ThreadPoolExecutor.AbortPolicy:该策略下,直接丢弃任务,并抛出 RejectedExecutionException 异常。

ThreadPoolExecutor.DiscardPolicy:该策略下,直接丢弃任务,什么都不做。

ThreadPoolExecutor.DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。

除此之外,还可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。

(4)任务放置的顺序过程

任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。 了解这部分就相当于了解了线程池的核心运行机制。

首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行 线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝 该任务。其执行过程如下:

首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队 列已满,则创建并启动一个线程来执行新提交的任务。

如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

其执行流程如下图所示:

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

(5)任务结束后会不会回收线程

final void runWorker(Worker w) {

Thread wt = Thread.currentThread();

Runnable task = w.firstTask;

w.firstTask = null;

w.unlock(); // allow interrupts

boolean completedAbruptly = true;

try {

while (task != null || (task = getTask()) != null) {

w.lock();

if ((runStateAtLeast(ctl.get(), STOP) ||

(Thread.interrupted() &&

runStateAtLeast(ctl.get(), STOP))) &&

!wt.isInterrupted())

wt.interrupt();

try {

beforeExecute(wt, task);

Throwable thrown = null;

try {

task.run();

} catch (RuntimeException x) {

thrown = x; throw x;

} catch (Error x) {

thrown = x; throw x;

} catch (Throwable x) {

thrown = x; throw new Error(x);

} finally {

afterExecute(task, thrown);

}

} finally {

task = null;

w.completedTasks++;

w.unlock();

}

}

completedAbruptly = false;

} finally {

processWorkerExit(w, completedAbruptly);

}

}

首先线程池内的线程都被包装成了一个个的

java.util.concurrent.ThreadPoolExecutor.Worker,然 后这个worker会马不停蹄的执行任务,执行完任务之后就会在while循环中去取任务,取到任务就继续执行,取 不到任务就跳出while循环(这个时候worker就不能再执行任务了)执行 processWorkerExit方法,这个方 法呢就是做清场处理,将当前woker线程从线程池中移除,并且判断是否是异常进入processWorkerExit方 法,如果是非异常情况,就对当前线程池状态(RUNNING,shutdown)和当前工作线程数和当前任务数做判断,是否要加入一个新的线程去完成最后的任务.

(6)未使用的线程池中的线程放在哪里

private final HashSet workers = new HashSet();

(7)线程池线程存在哪

private final HashSet workers = new HashSet();

19.Java多线程的几种状态及线程各个状态之间是如何切换的?

===============================

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

20.如何在方法栈中进行数据传递?

=================

通过方法参数传递;通过共享变量;如果在用一个线程中,还可以使用ThreadLocal进行传递.

21.描述一下ThreadLocal的底层实现形式及实现的数据结构?

==================================

Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null:

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建它们。

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量,防止出现内存泄漏。

阿里面试官首次分享完整版多线程核心题,你准备好跳槽了吗?

22.Sychornized是否是公平锁?

=====================

不是公平锁

23.描述一下锁的四种状态及升级过程?

===================

以下是32位的对象头描述

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-JhoNx0bJ-1711042144759)]
[外链图片转存中…(img-t6lPDseM-1711042144760)]
[外链图片转存中…(img-VT16po7I-1711042144760)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-3zvJkOTf-1711042144761)]

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

[外链图片转存中…(img-iYiE3yZY-1711042144761)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多选题: 1. 什么是锂离子电池的正极材料? A. 锂钴氧化物 B. 锂铁磷酸 C. 锂钛酸 D. 锂锰氧化物 答案:A 2. 以下哪种情况可能导致笔记本电池寿命缩短? A. 经常使用电池电量低于20% B. 经常使用电池电量超过80% C. 经常将电池充电到100% D. 经常将电池充电到50% 答案:A 3. 以下哪种电池技术具有更高的能量密度? A. 锂离子电池 B. 镍氢电池 C. 铅酸电池 D. 镍镉电池 答案:A 4. 以下哪种情况可能导致笔记本电池充电时间变长? A. 充电器功率不足 B. 电池寿命过长 C. 充电器和电池匹配不良 D. 电池温度过低 答案:A 5. 以下哪种技术可以延长笔记本电池寿命? A. 深度充放电 B. 每次使用电池都充电 C. 避免高温环境 D. 避免低温环境 答案:C 6. 以下哪种情况可能导致笔记本电池容量下降? A. 高温环境 B. 低温环境 C. 经常放置不用 D. 经常使用并充电 答案:A 7. 以下哪种电池技术具有更长的寿命? A. 镍镉电池 B. 镍氢电池 C. 锂离子电池 D. 铅酸电池 答案:C 8. 以下哪种情况可能导致笔记本电池不能充电? A. 充电器故障 B. 电池故障 C. 笔记本电脑故障 D. 以上都可能 答案:D 9. 以下哪种技术可以避免笔记本电池过度充电? A. 过放保护 B. 过充保护 C. 温度保护 D. 短路保护 答案:B 10. 以下哪种电池技术具有更高的安全性? A. 镍氢电池 B. 镍镉电池 C. 锂离子电池 D. 铅酸电池 答案:C 填空题: 1. 锂离子电池的正极材料是_________________。 答案:锂钴氧化物。 2. 电池寿命缩短的主要原因是_________________使用电量不当。 答案:经常。 3. 锂离子电池具有更高的_________________。 答案:能量密度。 4. 笔记本电池充电时间变长可能是因为充电器_________________不足。 答案:功率。 5. 延长笔记本电池寿命的有效方法是避免_________________环境。 答案:高温。 6. 笔记本电池容量下降的主要原因是_________________环境。 答案:高温。 7. 锂离子电池具有更长的_________________。 答案:寿命。 8. 笔记本电池不能充电可能是由于充电器、电池或笔记本电脑_________________。 答案:故障。 9. 避免笔记本电池过度充电的有效方法是设置_________________保护。 答案:过充。 10. 锂离子电池具有更高的_________________。 答案:安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值