JUC高并发编程相关的面试题。

JUC高并发编程相关的面试题

1.谈谈什么是JUC
JUC 是 java.util.concurrent工具包简称,是一个处理线程的工具包。JDK1.5开始出现

2.为什么要使用线程池
因为频繁的开启线程或者停止线程,线程需要从新被cpu从就绪到运行状态的调度,需要发生cpu的上下文的切换,效率非常的低。

3.你们哪儿些地方会用到线程池
实际的开发中禁止自己new线程所以必须使用线程池来维护和创建线程注意下:不会使用到jdk自带的Executor线程池,都是根据ThreadPoolExecutor构造函数来实现封装

4.线程池有哪些作用
线程池
数据库连接池
本质原理:复用机制
创建了一个线程不会被立即销毁。而是一直被复用。
提前创建好五个线程一直运行状态。避免cpu从就绪到运行状态的调度,从而提高效率。
核心点:复用机制提前创建好固定的线程一直在运行状态 实现复用 限制线程创建的数量。
1.降低资源的消耗:通过线程池技术重复利用已创建的线程,降低线程的创建和销毁造成的损耗。

2.提高响应速度: 任务到达时,无需等待线程创建即可立即执行·

3.提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

4.提供更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExector,就允许任务延期执行或者是定期执行。
5.线程池的创建方式
Executor.newCachedThreadPool();可缓存的线程池
Executor.newFixedThreadPool();可定长度的线程池
Executor.newScheduledThreadPool();可定时的线程池
Executor.newSingleThreadPool();单例线程池
底层都是基于ThreadPoolExecutor构造函数封装的
6.线程池的底层是如何实现复用的
本质思想:创建一个线程,不会立马停止或者销毁而是一直实现复用。

提前创建好固定大小的线程一直保持在正在运行的状态;(可能会非常的消耗cpu的资源)
当需要线程执行任务,将该任务缓存在并发队列中;如果缓存队列满了,则会执行拒绝策略;
正在运行的线程从并发队列中获取任务执行从而实现多线程的复用问题

线程池的核心点:复用机制
提前创建好固定的线程一直在运行的状态-----死循环实现
提交的线程任务缓存到一个并发队列集合中,交给正在运行的线程运行
正在运行的线程就从队列中获取该任务执行

简单模拟手写Java线程池:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
7.ThreadPoolExecutor核心的参数有哪些
corePoolSize:核心线程数量 一直正在保持运行的线程
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超过corePoolSize后创建线程的存活时间
unit:keepAliveTime的时间单位
workQueue:任务队列,用于保存待执行的任务
threadFactory:线程池内部创建线程所用的工厂
handler:任务无法执行时的处理器。
8.线程池创建的线程会一直在运行的状态吗?
不会
例如:配置核心线程数corePoolSize为2,最大线程数为maximumPoolSize为5,我们可以配置超出corePoolSize核心线程数后创建的线程存活时间为60s 在60s内没有核心线程一直没有任务执行,则会停止该线程。
9.为什么阿里巴巴不建议使用Exectors
因为Exectors线程池底层是基于ThreadPoolExecutor构造函数封装的,采用的无界队列存放缓存任务,会无限缓存任务发生内存溢出。会导致我们最大线程数会失效。
在这里插入图片描述
在这里插入图片描述
10.线程池的底层ThreadPoolExecutor底层实现原理
1.当线程数小于核心线程数时,创建线程
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程大于等于核心线程数,且任务队列已经满了
3.1若线程数小于最大线程数,创建线程
3.2若线程数等于最大线程数,抛出异常,拒绝任务

11.线程池队列满了,任务会丢失吗
如果队列满了,且任务总数》最大线程数则当前线程走拒绝策略
可以自定义异拒绝异常,将该任务缓存到redis本地文件、mysql中后期项目启动实现补偿

12.线程池拒绝策略类型有哪些呢
1.AportPolicy 丢弃任务,抛运行时异常
2.CallerunsPolicy执行任务
3.DiscardPolicy 忽视,什么都不发生
4.DiscardOldestPolicy 从队列里面踢出最先进入队列(最后一个执行)的任务
5.实现RejectedExecutionHandler接口,可自定义处理器

13.线程池如何合理的配置参数
自定义线程池就需要我们自己配置最大线程数maximumPoolSize,为了高效的并发运行当然这个不能随便配置。这个时候需要看我们的业务是IO密集型还是CPU密集型。

CPU密集型
CPU密集型的意思就是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有真正的多核CPU才能得到加速(通过多线程)而单核CPU上无论你多开几个模拟的多线程,任务都不可能得到加速,因为CPU的运算能力就那些。

CPU密集型任务配置尽可能少的线程数量:以保证每个CPU高效的运行一个线程。一般公式:(CPU核数+1)个线程池

IO密集型
io密集型,即该任务需要大量的io,即大量的阻塞。在单线程上运行io密集型任务会导致浪费大量的CPU运算能力浪费在等待。
所以在io密集型任务中使用多线程可以大大的加速程序运行,即使在单核的cpu上这种加速主要是利用了被浪费掉的阻塞时间。

io密集型时大部分线程阻塞,故需要配置线程数:
公式:CPU数*2
CUP核数/(1-阻塞系数)阻塞系数在0.8·0.9之间查看CPU核数: System.out.println(Runtime.getRuntime().availableProcessors());

14.什么是悲观锁,什么是乐观锁
悲观锁:
1.站在MySQL的角度分析:悲观锁就是比较悲观,当多个线程对同一行数据进行修改时,最后只会有一个线程才能修改成功,只要谁能够对获取到行锁则其他线程不能对该数据做任何操作,且时阻塞状态。
2.站在java的层面,如果没有获取到锁,则会阻塞等待,后期唤醒的锁成本就会非常高,从新被我们的CPU从就绪调度为运行状态。lock syn 锁悲观锁没有获取到锁的线程会阻塞等待;
行锁与表锁----
行锁与事务挂钩—
Begin开启事务
执行操作
Commit

乐观锁:
乐观锁比较乐观,通过预值和版本号比较如果不一致的情况则通过循环控制更改,当前的线程不会阻塞,是乐观效率比较高,但是乐观锁比较消耗cpu资源
乐观锁:获取锁–如果没有获取到锁,当前的线程是不会阻塞等待的通过死循环控制,没有竞争锁的概念。

15.Mysql层面如何实现乐观锁呢
在我们的表结构中,会新增一个字段‘version’ varchar(255) default null,
多个线程对同一行数据进行实现修改操作,提前查询当前最新的version版本号码。
作为update 条件查询如果当前的version版本号发生了变化则查询不到该数据。
表示为修改数据失败,则不断充实,有新查询最新的版本实现update。
注意控制下乐观锁的循环次数,避免cpu飙高的问题。
mysql的 innodb引擎中存在行锁的概念

16.乐观锁实现方式
使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式
Redis实现

17.java有哪些锁的分类呢
悲观锁与乐观锁
公平锁与非公平锁
自旋锁与重入锁
重量级锁与轻量级锁
独占锁与共享锁

18.公平锁与非公平锁之间的区别
公平锁:就是比较公平,根据请求锁的顺序排列,先来请求就先获取锁,后来获取锁就是最后获取到,采用的是队列存放类似于吃饭排队。
非公平锁:不是根据请求的顺序排列,通过争抢的方式获取锁
非公平锁效率比公平锁的效率高,synchronized是非公平锁
new ReentramtLock()(true)—公平锁
new ReentramtLock()(false)—非公平锁

19.公平锁的底层是如何实现的
公平锁:就是比较公平,根据请求锁的顺序排列,先来请求就先获取锁,后来获取锁就是最后获取到,采用的是队列存放类似于吃饭排队。
队列–底层实现方式–数组或者链表实现

20.独占锁与共享锁之间的区别
独占锁:在多线程中只有一个线程获取到锁,其他的线程会等待。
共享锁:多个线程可以同时持有锁,例如ReentrantLock读写锁。读读可以共享写写互斥,读写互斥,写读互斥。

21.什么是锁的可重入性
在同一个线程中锁可以不断传递的,可以直接获取
syn、lock aqs

22.什么是cas(自旋锁),他的优缺点
syn/lock
CAS
没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。
CAS:Compare and Swap,翻译成比较并替换。执行函数CAS(V,E,N)
CAS有3个操作数,内存值为V,旧的预期值为E,要修改的新值为N。当且仅当预期值E和内存值V相同时将内存值V改成N,否则什么都不做。
CAS是通过硬件指令,保证原子性 Java是通过unsafe jni技术
原子类: AtomicBoolean,AtomicInterger,AtomicLong 等使用CAS实现。

优点:没有获取到锁的进程,会一直处于在用的状态不会阻塞,没有锁的线程会一直通过循环控制重试。缺点:通过死循环控制,消耗cpu资源比较高,需通过控制循环次数,避免cpu飙高的问题

CAS本质的原理:
旧的预期值=======v(共享变量中值)才会修改我们的v

基于cas实现锁机制的原理
cas无锁机制原理
定义一个无锁的状态
状态状态值=0 则表示没有线程获取到该锁;
状态状态值=1 则表示有线程已经持有该锁;
实现细节;
cas获取锁:
将该锁的状态从0改为1----能够修改成功 cas成功表示获取锁成功
如果获取锁失败–修改失败,则不会阻塞而是通过循环(自旋来控制重试)
cas释放锁:
将该锁的状态从1改为0如果能够改成功 cas成功表示释放锁成功

23.CAS如何解决ABA的问题
CAS主要检查 内存值V与旧的预期值E是否一致,如果一致的情况下,则修改。
这时候会存在ABA的问题:
如果原来的值A改成了B,B又改成了A发现没有变化实际上已经发生了变化,所以存在ABA的问题。
解决办法:通过版本号码,对每个变量更新做版本号+1
解决aba的问题是否大:概念产生冲突但是不影响结果,换一种方式通过版本号码的方式。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
24.谈谈你对Threadlocal的理解
Threadlocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。
Threadlocal相当于提供了一个线程隔离,将变量与线程相绑定。
Threadlocal适用于多线程的情况下可以实现传递数据,实现线程隔离。
Threadlocal提供给我们每个线程缓存局部变量
Threadlocal基本api
1.new Threadlocal();–创建Threadlocal
2.set 设置当前线程绑定的局部变量
3.get 获取当前线程绑定的局部变量
4.remove()移除当前线程绑定的局部变量

25.哪些地方使用Threadlocal?
Spring事务模板类
获取HttpRequest
AOP调用链

26.Threadlocal底层实现原理
在每个线程中都有自己独立的ThreadlocalMap对象,中Entry对象。
如果当前线程对应的ThreadlocalMap对象为空的情况下,则创建该ThreadlocalMap对象,并赋值键值对。

27为什么线程缓存的是ThreadlocalMap对象
ThreadlocalMap可以存放n各不同的Threadlocal对象;
每个Threadlocal只能对应一个变量值;
ThreadlocalMap<Threadlocal对象,value> threadlocalMap
Threadlocal.get();
ThreadlocalMap.get(Threadlocal)—缓存变量值。

28.谈谈强,软,弱,虚引用区别
强引用:当内存不足时JVM开始进行GC(垃圾回收),对于强引用对象,就算是出现了OOM(内存用完了)也不会对该对象进行回收。

软引用:当系统内存充足时候,不会被回收;当系统内存不足时它会被回收,软引用通常用在对内存敏感的程序中,比如告诉缓存就用到的软引用,内存够用时就保留,不够时就回收。

弱引用:弱引用需要用到java.lang.ref.WeakReference类来实现,它比软引用的生存周期更短。对于只有弱引用的对象来说,只要有垃圾回收,不管JVM的内存是否够,都会回收该对象占用的储存空间。

虚:虚引用需要java.lang.tef.phantomreference类来实现。顾名思义,虚引用就是形同虚设。与其他的几种引用不同,虚引用并不会决定对象的生命周期。

29.ThreadLocal为何引发内存泄漏的问题
补充概念:什么时内存泄漏问题,内存泄漏就表示我们程序员申请了内存,但是该内存一直无法释放;
内存泄漏溢出问题:
申请内存时,发现申请的内存不足,就会报错,内存溢出的问题;

因为每个线程都有独立的ThreadLocalMap对象key作为ThreadLocal指向null的时候,Entry对象中的key值就会变成null,GC如果没有清理垃圾时,则该对象会一直无法被垃圾回收机制回收。一直占用到系统内存,可能会发生内存泄漏的问题。

30.如何避免ThreadLocal内存泄漏问题
1.可以自己调用remove方法将不要的数据移除避免内存泄漏的问题;
2.每次使用set方法的时候会清除之前的key为null;
3.ThreadLocal对象为弱引用

31.ThreadLocal采用的弱引用而不是强引用
1.如果key是为强引用:当我们将ThreadLocal引用指向null,但是每个线程都有独立的ThreadLocalMap还一直在继续持有对象,但是我们的ThreadLocal对象不会被回收,就会发生内存泄漏的问题。

2.如果key为弱引用:当我们将ThreadLocal的引用指向为null,entry中的key指向为null但是下次调用set方法时会判断如果key空的情况下,直接删除,避免了entry内存发生泄漏的问题。

3.不管是强引用还是弱引用都会发生内存泄漏的问题。弱引用中的ThreadLocal不会发生内存泄漏的问题。

4.但是最终根本的原因ThreadLocal内存泄漏问题产生于ThreadLocalMap与我们当先线程的生命周期一样,如果没有手动删除的情况下,就有可能发生泄漏的问题。

(强制)在代码逻辑中使用完成ThreadLocal,都要调用remove() 方法,及时清理。
(推荐)尽量不要使用全局的ThreadLocal

32.谈谈lock锁的实现原理
底层基于AQS+CAS+LockSupport锁实现

33.synchronized与lock之间的区别
1.lock是基于aqs封装的锁结合cas实现,而lock锁的升级需要自己实现
2.synchronized是基于C++虚拟机的封装,JDK1.6优化可以实现锁的升级过程
3.lock每次获取锁和释放锁都需要靠自己实现,而synchronized底层就已经实现好了

34.谈谈LockSupport的用法
在这里插入图片描述
35.AQS的核心参数有哪些呢
node结点 采用双向链表的方式存放正在等待线程waitStatus状态,Thread等到锁的线程CANCELLED,值为1,表示当先线程被取消;
SIGNAL,值为-1.释放资源后需要唤醒后继节点。
CONDITION的值为-2等待condition唤醒。
PROPAGATE,值为-3工作于共享锁的状态,需要向后传播,比如根据资源是否剩余,唤醒后续节点。值为0表示当前节点在sync队列中,等待着获取锁。
head头节点 等待队列的头节点
tail 尾节点 正在等待的线程
state 锁的状态0无锁1已经获取锁,当前线程重入不断加1
exclusiveOwnerThread记录锁的持有核心方法:
//重试获取锁,如果小于0的情况则当前线程追加到链表中
tryAcquireShared(arg)
//如果为true的情况下,则开始唤醒被阻塞的线程
tryReleaseShared(arg)

36.lock锁的底层实现原理
1.lock锁原理 基于JavaAQS类封装在获取锁的时候AQS有一个状态的state+1,当线程不断的重入的时候都会不断的加1+,当释放锁的时候state-1;最终state为0该锁没有被任何线程获取到,没有抢到锁的线程,会存在一个双向的链表中
2.公平锁与非公平锁在获取锁的时候多了一个判断(!hasQueuedPredecessors()&)阻塞和唤醒用的apilocksupport,为了这个效率只会唤醒阻塞队列中head节点.next线程
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
aqs底层实现原理:
状态0,1 5,7>1当前线程重入加1
状态 0=====当前我们没有被任何线程所持有

状态 0=====1锁已经被其他线程持有

有一个没有获取到锁的线程集合(双向链表实现)
new Lock();
a(){
Lock.lock();状态=1
Lock.unlock();状态=1-1=0
}

b(){
Lock.lock();状态=====2
Lock.unlock();状态=2-1=1
}

c(){
Lock.lock();状态=3
Lock.unlock();状态=3-1=2
}

37.Semaphore信号量的底层原理
1.Semaphore用于限制可以访问某些资源(物理或者逻辑的)的线程数目,他维护了一个许可征集的集合,有多少资源限制就维护多少许可证集合。假如这里有n个资源,那就对应n个许可证,同一时刻只能有n个线程访问。一个线程获取许可证就调用acquire方法,用完释放资源就用release方法。
2.可以简单理解为Semaphore信号量可以实现对接口的限流,底层是基于aqs的实现
Semaphore简单的用法:
在这里插入图片描述Semaphore的工作原理
可以设置Semaphore信号量的状态state值为5当一个线程获取到锁的情况下,将state-1,释放之后state+1
当state<0,表示没有锁的资源,则当前线程阻塞

38.CountDownLatch底层原理
CountDownLatch的源码分析
CountDownLatch是一种Java.unit.concurrent包下一个同步工具类,它允许一个或者多个线程等待直到在其他线程中一组操作执行完成。和join方法非常相似
CountDownLatch底层原理是基于AQS实现的
CountDownLatch(2)AQS的state状态为2
调用CountDownLatch.countDown();方法的时候状态-1 当AQS状态state为0的情况下,则唤醒正在等待的线程。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值