2022-多线程面试

1.为什么会产生死锁?

线程1持有A资源,等待B资源,线程2持有B资源等待A资源,,线程1、2都持有各自的资源,不释放,这样就会陷入循环等待,就会产生死锁。解决答案是,线程1、2释放掉各自持有的资源。

教科书:

1.首先发生静态资源有限,即互斥

2.线程1、2各自持有静态资源,陷入循环等待

3.循环等待之后,如果1拿不到2的资源,那么释放,此时不会产生死锁,循环等待且不可剥夺,也不释放资源,所以叫请求保持条件。

2.wait和sleep的区别

区别:

从表面上来看:wait来自Object类,sleep来自Thread类

1.由于sleep针对的是线程,所以不可能是Object类的方法,理应在Thread类中,

2.wait在syn中,上的是sysn的锁,与锁相关,与对象头相关,所以理应属于对象

从深度原理上来说:

wait和锁相关,会释放锁,而sleep是线程本身,不涉及释放锁,

3.作用范围:sleep任何地方,wait只用在同步控制块或方法。

联系:

两者都会产生阻塞,

两者都会被中断唤醒,sleep中断唤醒(interupt方法)后会产生InterruptException异常,try、catch补货后可继续执行

3.进程和线程的区别

首先在任何一个操作系统中,必须执行程序的指令,指令是cpu识别的唯一东西,Cpu只知道指令,只会不断的执行指令,转移过去就需要保存指令,这个指令就叫做程序,保存程序有两种格式,linux中为ELF,windows中是exe文件,当我们执行程序的时候会把这些二进制的代码,还她所需要的数据,加载到内存中,开辟一个空间,这个空间就叫做进程的资源,就叫做一个进程。

在linux操作系统实现来说,进程和线程是没有区别的,进程和线程的控制块都是同一个,就是taskStrupt,如果两个进程共享了一个资源,我们就称两个进程为两个线程

比如A是进程,当A进程Fork出了一个线程B和他共享内存资源,那么A、B就成为线程。

 

4.Java线程的生命周期

线程的的生命周期只有三个,运行,阻塞,销毁。

由于Jvm采用了Linux线程来模拟了Java的线程,在java中创建的线程就是Thread对象,并不是操作系统线程,那么此时就需要先创建一个java的对象,再创建一个原生操作系统的线程,执行Thread中的run方法, 所以此时需要补充一个状态,就是new了一个java中的Thead对象但是还没有把原生操作系统线程分配给java对象的状态,叫做创建状态。

当创建好线程后,挂到操作系统上,等待调度,等待调度的阶段是阻塞的,阻塞在可调度的阶段,有别的线程在占用Cpu时间片,所以从阻塞状态切割出一个状态就叫就绪态,即为就绪等待执行状态。

如果在执行阶段调用了IO请求或者是去拿一把锁,此时拿不到锁,拿不到资源,就会发生阻塞状态。一种请求IO,一种拿到锁,此时Java为了辨别者两种情况,引入了两种状态一个阻塞,一个等待。

当运行完成之后,把原生操作系统资源还给了操作系统,但是Thread对象并没有回收,这时Thread对象为销毁也称终止状态。

5.程序开多少线程合适

开多少线程是和我们线程的性质完全相关的,看我们的业务是做什么的,

如果是完全压缩的这种,就属于CPU密集型,那么为了减少线程的上下文切换的时间,所以要求我们的CPU一直持有一个线程即单核CPU ,那么此时就无需进行线程切换,那么此时我们的线程数保持和CPU核心数一样即可。

如果是完全IO操作这种,就属于IO密集型,就看IO停顿多少时间,如果是1S两秒,那么就需要开辟较多的线程处理,假如资源无限大,那么需要开无限多的线程。如果又要节约系统资源,又要保证高性能,取折中方案,那么就线程数量开为CPU核心数的2倍。

6.notify和notifyAll的区别

面试官想问的是Sync底层的原理:

Object中,调用wai之后会放入到阻塞队列waitQueue中,这时候如果调用notify是随机唤醒一个,进入锁池即竞争队列entryList,如果是notifyAll的话,就会等待队列中的所有线程,放入竞争队列。

7.描述下Synchronized和lock区别

答:总-分-总

先说下Sync,java中最基本的互斥同步(保证共享数据在同一时刻只被一条线程访问)的手段就是Sync了。Sync经过编译后,会在同步代码块前后产生monitorEnter和monitorExist两个字节码指令,在只给你monitorEnter指令的时候,判断如果当前对象没有锁,或者当前线程已经持有锁(可重入),那么锁计时器加1,而在执行monitorExit指令时会减1,一旦锁计时器为0,锁立即被释放。如果获取锁失败那么当前线程就会被阻塞等待,直到对象释放锁为止。

因为阻塞和唤醒的操作,会让操作系统来帮忙,就会产生内核态到用户态的消耗,而虚拟机本身也会对其进行优化,比如在阻塞线程之前,加入一段时间的自旋等待CAS,以避免频繁的内核态到用户态的切换,所以出现了Lock。

reetrantlock是在JDK1.5的时候出现的,此时Sync还未进行优化,性能较差,连锁升级都没有,所以引入了Lock,历史问题。

而Sync还有个诟病,就是你只有一种条件,要不wait要不notify。

ReetrantLock有一些高级功能:

等待可中断:当前持有锁的线程,长期不释放锁的时候,正在等待的线程可以放弃等待,处理其他事情。等待可中断对于处理时间较长的同步块很有帮助。

支持公平锁:sync默认为非公平,必须按照申请时间来一次获取锁。ReetrantLock为默认为非公平锁,可以指定为公平锁,但是性能会有所下降

锁绑定多个条件变量:ReetrantLock中的条件变量,他可以切割出多个条件变量,多次调用newCondition即可。

8.ABA问题

线程1、2同时修改一个变量值A变为B,

线程1将A--》B,此时线程2因为某些原因阻塞了,

但是此时来个线程3将B的值又修改了为A,

此时线2获取到CPU时间片,发现变量值为预期A,所以又将A-》B

可以通过加版本号和时间戳A1-B1-A2

假如小明有个女朋友小红,后来他分了小红找了个男朋友小亮,但是最终小明又和小红在一起了。

这就是ABA问题。言简意赅

解决方案,加版本号,每次修改+1。

 

9.锁升级

轻量级锁:不存在线程竞争同一把锁,mark work中头标志位为00

重量级锁:当更新操作失败时,证明最少有一个其他线程在竞争抢锁,头部标志位为01

偏向锁:偏向于第一个线程,没有同步和CAS自旋操作。

 

10.什么是并发,什么是并行?会造成什么影响

并发就是,我们做核酸,每个人做2分钟,做完之后,下个人才可以做,时间有20分钟,这就是并发。当把时间缩短到每个学生考100ms的时候,就是CPU的原理。

并行就是,这个5分钟之内可以有多个学生考试。

并发安全,就是做核酸的时候,人很多,但是同一时间只能给一个人做,但是人们肯定不会遵守纪律,需要插队,所以需要一个管程,让人们排好队,不允许插队,这就是互斥同步。

11.CAS执行原理

6feeff064c294af48d0a63ab8bbe2ae7.png

 一种是通过原语,lock cmpxchg(compare change)下的lock dec;lockincr;lock前缀将cpu总线锁住,保证同一时间只有一个线程可以执行。

一种是通过关中断(关闭时钟中断,不让CPU进行线程切换),保证lock dec和lock incr是原子操作

 

ff045af5be4e4714bfe5486ccba65c4b.png

 

12.为什么要使用volitile

 

13.AQS面试

AQS字面意思是抽象的队列同步器,是用来构建锁或者其它同步器组件的基石。
通过内置的FIFO(先进先出)队列来完成线程获取锁的排队工作,通过一个state的int变量表示持有锁的状态。

AQS的流程

1.假设A,B,C三个线程 依次去抢一个同步锁,首先A先抢到锁把state置为1(3个状态:没占用是0,占用了是1,大于1是可重入锁),将自己设置为线程持有者ExclusiveOwnerThread

2.B线程进来后先尝试获取锁tryAcquire(),获取不到添加到队列中addWaiter(),如果队列为空,先创建傀儡节点,再把B添加到傀儡节点后边。

3.B线程调用acquireQueued(),将傀儡节点waitStatus设置为-1,最后调用LockSupport.part(this)方法将自己阻塞

4.C线程进来后也是先尝试获取不到锁,然后添加到B节点后,将B节点的waitStatus设置为-1,最后调用LockSupport.part(this)方法将自己阻塞

5.当A线程解锁后,将state置为0,将线程持有者置为null。最后将队列头节点waitStatus置为0,调用LockSupport的unpark方法,唤醒head节点下一个节点也就是B节点

6.B线程唤醒后,接着执行acquireQueued()方法,尝试获取锁成功,然后自己成为头节点,原来头结点指向null方便GC

7.B线程解锁后,C线程同上

 

14、ThreadLocal

1.ThreadLocal 是java中所提供的线程本地存储机制,可以利用该机制将数据(如对象)缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据
2.ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
3.如果线程池中使用ThreadLocal会造成内存泄漏,因为当使用完ThreadLocal对象后,理应当把设置的key、value,也就是Entry对象进行回收,但是线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏。解决方法,在一个线程使用完ThreadLocal后,手动调用ThreadLocal对象的remove方法移除Entry对象。
4.ThreadLocal经典应用场景是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享一个连接)
 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值