美文网首页
105 并发编程

105 并发编程

作者: 滔滔逐浪 | 来源:发表于2023-05-28 17:32 被阅读0次

JUC 相关面试题
谈谈什么事线程池
线程池和数据库连接池类似,可以统一管理和维护线程,减少没有必要的开销。

为什么使用线程池

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


image.png

你们哪些地方会使用到线程池

实际开发中 禁止自己new 线程
必须使用线程池来维护和创建线程
线程池有哪些作用

核心点: 复用机制 提前创建好固定的线程一直在运行状态 实现复用 限制线程创建数量。
1,降低资源消耗: 通过池化技术复用利用已创建的线程,降低线程创建和销毁造成的损耗。

  1. 提高线程的可管理性: 线程是稀缺资源,如果无限创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
    3.提高响应速度:任务到达时,无需等待线程创建即可执行。
    4.提供更多更强大的功能,线程池具备可拓展性,允许开发人员向其中增加更多的功能,比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
    线程池的创建方式
    可缓存线程池 ,可定长度 限制最大线程数,可定时线程池,单例线程池,底层都是基于ThreadPoolExecutor 构造函数封装。
    线程池底层是如何实现复用的
    本质思想: 创建一个线程,不会立马停止或者销毁而是一直实现复用。
    1,提前创建固定大小的线程一直保持在正在运行状态:(可能会非常损耗cpu的资源)
    2,当需要线程执行任务,将该任务提交缓存在并发队列中:如果缓存队列满了,则会执行拒绝策略;
    3,正在运行的线程从并发队列中获取任务执行从而实现多线程复用问题;


    image.png

    线程池核心点:复用机制------
    1.提前创建好固定的线程一直在运行状态-----死循环实现
    2.提交的线程任务缓存到一个并发队列集合中,交给我们正在运行的线程执行。
    3,正在运行的线程就从队列中获取该任务执行。
    ThreaPoolExeCutor 核心参数有哪些
    corePoolSize: 核心线程数量 一直在保持运行的线程
    maximumPoolSize: 最大线程数,线程池允许创建的最大的线程数
    keepAliveTime: 超出corePoolSize后创建的线程的存活时间。
    unit: keepAliveTime的时间单位
    workQueue: 任务队列,用于保存待执行的任务。
    threadFactory: 线程池内部创建线程所用的工厂。
    handler: 任务无法完成执行时的处理器。

线程池创建的线程会一直在运行状态吗?
不会
例如: 配置核心线程数corePoolSize 为2,最大线程数 maximumPoolSize 为5
我们可以通过配置超出 corePoolSize 核心线程数后创建的线程的存活时间例如为 60s
在 60s 内没有核心线程一直没有任务执行,则会停止该线程。

为什么阿里巴巴不建议使用Executors
因为默认的Executors线程池底层是基于ThreadPoolExecutor 构造函数封装的,采用无界队列存放缓存任务,会无限缓存任务容易发生内存溢出,会导致我们最大线程数会失效。


image.png

线程池底层ThreadPoolExecutor 底层实现原理。
1.当线程数小于核心线程数时, 创建线程;

  1. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列;
    3,当线程数大于等于核心线程数,且任务队列已满;
    3.1 若线程数大于小于最大线程数,创建线程
    3.2 若线程数等于最大线程数。抛出异常,拒绝任务。

线程池队列满了,任务会丢失吗
如果队列满了,且任务总数> 最大线程数 则当前线程走拒绝策略。
可以自定义拒绝异常,将该任务缓存到redis,本地文件,mysql 中后期项目启动实现补偿。
1,丢弃任务,抛运行时异常;
2,执行任务;
3.忽视,什么都不会发生;
4,从队列中踢出最先进队列的任务;
5,实现Handler接口,可自定义处理器;

什么是悲观锁?什么是乐观锁
悲观锁:

  1. 站在mysql的角度分析:悲观锁就是比较悲观,当多个线程对同一行数据实现修改的时候,最后只有一个线程才能修改成功,只要谁能对获取到行锁则其他线程时不能够对该数据做任何修改操作,且是阻塞状态。
  2. 站在java 锁层面,如果没有获取到锁,则会阻塞等待,后期唤醒的锁的成本就会非常高,从新被我们cpu从就绪状态调度为运行状态
    Lock syn锁 悲观锁从没有获取到锁的线程会阻塞等待。
    乐观锁:
    乐观锁比较乐观,通过预值或者版本号比较,如果不一致性的情况则通过循环控制修改,当前线程不会被阻塞,是乐观,效率比较高,但是乐观锁比较消耗cpu资源。
    乐观锁:获取锁----如果没有获取到锁,当前线程是不会阻塞等待 通过死循环控制。
    乐观锁属于无锁机制,没有竞争锁流程。
    java 有哪些锁的分类呢
    1,悲观与乐观锁
    2,公平锁与非公平锁
    3,自旋锁/重入锁
    4,重要级锁与轻量级锁
    5, 独占锁与共享锁

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

New ReentrantLock(true) --公平锁
new ReentrantLock(false) 非公平锁
底层基于aqs实现。
公平锁底层是如何实现的
公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁的就最后获取到,采用队列存放
队列--底层实现方式--数组或者链表实现。
独占锁与共享锁之间的区别
独占锁:在多个线程中,只允许有一个线程获取到锁,其他线程都会等待。
共享锁: 多个线程可以同时持有锁,例如 ReentrantLock 读写锁。读读可以共享。写写互斥,读写互斥,写读互斥。

什么是锁的可重入性。
在同一个线程中锁可以不断的传递,可以直接或者
Syn/lock
aqs
什么是CAS(自旋锁)它的优缺点
Syn/lock
CAS
没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。
CAS: Compare and SWAp 翻译成比较和交换,执行函数 CAS(V.E,N)
CAS有三个参数,内存值V,旧的预期值E,要修改的新值N 当且仅当预期值E和内存值V相同时,将内存值修改为N,否则什么都不做。
![image.png](https://img.haomeiwen.com/i12197462/4fb5fa0
传中.. (image.png-406f1b-1685931908100-0)]

1,cas 是通过硬件指令,保证原子性
2, java 是通过unsafe jni 技术
原子类: AtomicBoolean, AtomicINteger,AtomicLong 等使用CAS实现
优点: 没有获取到锁的线程,会一直在用户态,不会阻塞,没有获取到锁的线程会一直通过循环控制重试。
缺点: 通过死循环控制,消耗CPu资源比较高,需要控制循环次数避免cpu飙高的问题。
cas本质的原理:
旧的预期值===V(共享变量中值),才会修改我们v.

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

CAS如何解决 ABA的问题
Cas主要检查 内存值V与旧的预估值=E 是否一致,如果一致的情况下,则修改。
这时会出现ABA的问题:
如果将原来的值A,改为了B,B 又改为了A 发现没有发生变化,实际上已经发生了变化,所以存着aBA的问题。
解决办法,通过版本号码,对每个变量更新的版本号码做+1

谈谈你对Threadlocal 理解
ThreadLocal提供了线程本地变量,他可以保证访问到的变量属于当前线程,每个线程都保存一个变量副本,每个线程的变量都不相同。ThreadLocal相当于提供了一种线程隔离,将变量和线程相绑定。
Threadlocal 适用于多线程的情况下,可以实现传递数据,实现线程隔离。
ThreadLocal 提供给我们每个线程缓存局部变量。
Threadlocal 基本API
1.new Threadlocal();----创建Threadlocal
2 set 设置当前线程的局部变量
3 get 获取当前线程绑定的局部变量
4, remove() 移除当前线程绑定的变量。
那些地方有使用threadlocal
1,spring 事务模板类
2,获取HttpRequest
3, Aop 调用链

Threadlocal 底层实现原理。
1,在每个线程中都有独立的ThreadLocalMap 对象,中Entry对象;
2,如果当前线程对应的ThreadLocalMap 对象为空的情况下,则创建该ThreadLocalMap对象,并且赋值键值对。
key 为当前new ThreadLocal 对象,value就是为object变量值。

相关文章

网友评论

      本文标题:105 并发编程

      本文链接:https://www.haomeiwen.com/subject/sdjdedtx.html