5000字,多线程面试题附带答案

前言

在这里插入图片描述

大家好,我是台州光头佳
纵观我的面试经历,多线程方面的面试问题出现的频率肯定是前三的地位。
虽然你我开发中接触到多线程并发开发的机会较少,
奈何却是面试过程中的常客,希望我总结的能帮助到你

认知

Q:为什么需要多线程?
A:通俗来说为了提高效率,毕竟四根水管总比一根水管蓄水快,
现在电脑都是多核CPU,多线程可以发挥其应有的速度。

Q:如何创建线程?
1.继承Thread类
2.实现Runable接口(无返回值)
3.实现Callable接口(有返回值)
4.利用线程工厂来创建线程(本质还是通过继承Thread类)

Q:线程有几种状态?
1.创建状态:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
2.就绪状态:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
3.运行状态:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
4.阻塞状态:线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
5.死亡状态:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。

Q:你常用调整状态的方法有?
1.sleep():睡眠,让当前线程停止执行,把cpu让给其他线程执行,但不会释放对象锁和监控的状态,到了指定时间后线程又会自动恢复运行状态。
2.**wait()notify()**属于Object类,与sleep()的区别是当前线程会释放锁,进入等待此对象的等待阻塞,而调用notify()唤醒阻塞的线程。
3.join():插队,使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。
4.yield():礼让,暂停当前正在执行的线程对象,并执行其他线程。

Q:什么是守护线程?它的作用是什么?
A:守护线程顾名思义是一个线程守护另一个线程,setDaemon(true)的方式开启,
作用是为其他线程运行提供便利服务,比如jvm在进行垃圾回收的时候就有守护线程协助。

Q:线程之间的通讯方式?
1.wait()和notify()的阻塞唤醒的方式。
2.共享变量的方式:例如使用volatile关键字。
3.使用JUC工具类 CountDownLatch。
4.基本LockSupport工具实现线程间的阻塞和唤醒。

Q:谈一谈线程中ThreadLocal
A:
1.是什么?:ThreadLocal是一种副本变量,每一个线程都存在自己独有,在并发开发的时候互不影响,
2.作用?:主要作用是作为存储数据的类。
3.样例?:例如Spring实现事务隔离级别的源码中就有使用TreadLocal的情况,
Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
4.实现原理?:观看get()源码发现每一个Thread都维护了一个自己的threadLocals变量实现隔离性,存储在ThreadLocalMap中。
5.ThreadLocalMap的数据结构?:基于数组形式的Entry键值对结构。
6.使用缺陷?:观看ThreadLocalMap源码时可以发现继承WeakReference的弱引用,那么就会出现key被GC的时候value不能被回收掉,那么在大量使用的时候会出现内存溢出,因此最好需要手动调用remove清空。

Q:线程之间存在执行顺序吗?
A: 线程的调度是由CPU决定,而CPU执行具有不确定性,因此线程执行时无序但是我可以通过join()等方式手动有序。
并且我们无法通过调整线程优先等级来保证执行顺序只是提升优先度。

Q:你知道正确的停止线程的方式吗?
A:一般来说我们无法知道线程执行的情况,无法强制中断线程,
因此使用interrupt方法,使线程异常,线程进行捕获或抛异常,正常执行完run方法终止。

线程安全

Q:什么是线程安全?
A:可以看看我这篇文章:什么是线程安全

Q:synchronize和Lock类的区别?

区别synchronizeLock类
表象关键字工具类
作用域修饰代码块,修饰方法 ,修饰静态方法,修饰类代码块
实现原理synchronize是通过jvm内部一种叫monitor监视锁机制实现锁,简单来说执行代码的时候需要先通过monitorenter指令获取监视锁,然后执行完毕后monitorexit指令释放监视锁。依赖CAS 和 AQS机制
锁释放自动手动
锁状态无法判断可以判断
锁类型可重入,非公平可重入,可公平,可中断
使用情况常用,使用于粒度较小常用,使用根据业务判断

Q:synchronize中的锁优化?
A:jdk1.6中对synchronize进行了优化,从无锁到偏向锁、轻量级、重量级的变更。在这里插入图片描述
1.从无锁到偏向锁:当线程尝试获取锁的时候,首先检查这个对象头的MarkWord是不是储存着这个线程的ID。
如果是,那么直接进去而不需要任何别的操作,锁升级为偏向锁以后再进入不需要重复获取。
2.偏向锁升级轻量锁:当出现多个线程竞争偏向锁导致偏向锁升级为轻量级锁,其中轻量级锁添加了CAS操作来增加效率。
3.轻量级锁升级为重量级锁:如果判断CAS当前线程锁对象被其它的线程抢占,那么锁就会升级为重量级锁通过监视锁来实现。

Q:你在开发过程中更多使用synchronize还是ReentrantLock?
A:
1.我更喜欢synchronize,因为使用比较方便且不需要自己手动释放锁。
2.我更喜欢ReentrantLock,因为它可实现公平,手动中断,操作性比较好。

Q:ReentrantLock如何实现公平锁?
A:具体可以查询源码FairSync(),简单来说有个FIFO的队列,公平与非公平的区别在于多了一层判断,
判断当前线程是不是队列的首部,如果是才能获取锁。

Q:什么是CAS?
A:CAS的全名是比较与交换算法,利用预期值,更新值,内存值,三个参数实现。
具体可以参考这个通俗易懂CAS算法

Q:CAS的缺陷?
A:
1.经典的ABA问题
2.可能会出现循环过长
3.只能保证一个共享变量的局限

Q:什么是AQS?
A:全称:AbstractQueuedSynchronizer,中文我们称呼为抽象队列加载器或者抽象队列同步器,
实现过程:两个核心参数state变量、加锁线程。
1.首先state的默认值是0,假如有线程A获取锁,通过用CAS操作判断如何是0,那么就可以获取锁将state变为1,
2.记录A线程获取锁,如果A线程再次获取锁,那么state就变为2再次加1,这个就是可重入锁的实现。
3.这个时候B线程也想获取锁,通过用CAS操作判断state已经是1,证明已经被别的线程获取,
4.那么线程B会被放入一个等待队列中排队,等待A线程将释放锁后将state值改为0

Q:能再说说这个等待队列吗?B线程又什么时候去进行CAS操作?
A:据我所知这个是一个FIFO队列,内部是一种叫CLH的锁机制,它是一种自旋公平锁,
1.首先有一个尾节点指针,通过这个尾结点指针来构建等待线程的逻辑队列,因此能确保线程线程先到先服务的公平性,因此尾指针可以说是构建逻辑队列的桥梁。
2.通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量将由前一个线程写入。由于某个线程获取锁操作时总是通过尾节点指针获取到前一线程写入的变量。
因此通过自旋等待的方式,当A线程完成锁释放变更锁状态就会去获取锁。

Q:state中的值是怎么同步到多线程中的?
A:通过votiale关键字的可见性

Q:死锁达成的四个条件是?
1.互斥
2.请求与保持
3.无法剥夺
4.循环等待

线程池

Q:为什么要使用线程池或者好处是什么?
A:
1.提高效率:当需要创建线程时,可以使用线程池内创建好的线程,无需等待。
2.降低消耗:通过重复利用已创建的线程降低创建和销毁的消耗。
3.方便管理:线程池可以统一分配、监控和调休。

Q:线程池的核心参数有哪一些?
A:
1.最大线程数:maximumPoolSize
2.核心线程数:corePoolSize
3.活跃时间:keepAliveTime
4.阻塞队列:workQueue
5.拒绝策略:RejectedExecutionHandler

Q:你知道那些类型的线程池?
A:
1.newCachedThreadPoo-可缓存线程池
2.newFixedThreadPool -定长线程超出等待
3.newScheduledThreadPool -定长线程池,支持定时
4.newSingleThreadExecutor -工作线程执行任务

Q:你知道线程池的提交流程或者工作流程吗?
A:
1.提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务。
2.当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队。
3.如果阻塞队列满了并且也超过了设置的最大线程数,那么就会执行拒绝策略。
4.期间有线程超过活跃时间要进行销毁。
在这里插入图片描述

Q:线程池的拒绝策略有哪些?
A:
1.AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
2.CallerRunsPolicy:只用调用者所在的线程来处理任务
3.DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
4.DiscardPolicy:直接丢弃任务,也不抛出异常

Q:keepLive存活时间怎么判断?
A:不复杂比想象中的简单,getTask()方法线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime拿不到task,默认为线程需要销毁。

Q:线程池的工作队列有哪些?
A:
1.ArrayBlockingQueue:基于数组结构的有界阻塞队列
2.LinkedBlockingQueue:基于链表结构的阻塞队列
3.PriorityBlockingQueue:具有优先级的无限阻塞队列
4.SynchronousQueue:不存储元素的阻塞队列
5.DelayQueue:延迟队列

Q:在使用LinkedBlockingQueue这种无界工作队列有什么问题?
A:我们在使用无界队列时需要注意JVM内存溢出。

Q:为什么需要工作队列?
A:因为创建线程的时候需要先获取锁,此时其他线程会被阻塞。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值