并发编程-2024大厂面试真题
2024-10-10 09:47:49 0 举报
2024大厂面试真题 java多线程、并发编程
作者其他创作
大纲/内容
一、Java中怎么唤醒一个阻塞的线程?(小红书)
唤醒线程的方式:
interrupt()方法
Unsafe.unpark()方法
首先,线程阻塞的方式,在Java中其实只有一种形式,就是Unsafe类中的park方法去挂起的。
只不过Java中针对这种阻塞的状态,细分了3种:
只不过Java中针对这种阻塞的状态,细分了3种:
BLOCKED
WAITING
TIMED_WAITING
这三个状态,其实对于操作系统来说,没区别,Java中细分的目的是为了让咱们在排查问题时,可以更好的定位。
Java线程在获取synchronized锁资源失败后,如果该线程执行了interrupt,那这个线程会被唤醒吗?
synchronized加锁的核心逻辑都是在C++内部实现的,如果在基于synchronized挂起后,线程被执行了 **interrupt** ,在C++的代码中,会被唤醒,并且可以执行CAS尝试获取锁资源,但是一般情况下,拿不到就再次被挂起了。而在Java中能看到的效果就是,没有任何效果(没拿到锁)。
其他的类似WAITING和TIMED_WAITING必然也是可以唤醒的,只是根据后续工具的代码逻辑来决定是否能真正的唤醒到自己编写的业务代码中。
比如lock.lock方法,synchronized的阻塞形式,中断后不一定会回到你自己的业务代码中。
但是比如lock.lockInterruptibly,Thread.sleep(),等可以在中断后,抛出interrupt异常能看到效果。
其他的类似WAITING和TIMED_WAITING必然也是可以唤醒的,只是根据后续工具的代码逻辑来决定是否能真正的唤醒到自己编写的业务代码中。
比如lock.lock方法,synchronized的阻塞形式,中断后不一定会回到你自己的业务代码中。
但是比如lock.lockInterruptibly,Thread.sleep(),等可以在中断后,抛出interrupt异常能看到效果。
二、多个任务,同时达到临界点,主线程执行,怎么实现(去哪)
join()
CountDownLatch
FutureTask
三、CountDownLatch和CyclicBarrier,分别作用于什么业务,哪个可以复用,为什么;(去哪)
CountDownLatch的应用场景:
CyclicBarrier的应用场景:
CountDownLatch是基于AQS中的state做计数,每完成一个任务,countDown方法执行后,会对state - 1,当state为0后,就会唤醒那些基于CountDownLatch执行await的线程。
CyclicBarrier是自己搞了一个count属性,每当有一个线程到位 (执行CyclicBarrier的await方法) 之后,就会对count进行--操作。等到count计数到0后,依然会唤醒,可以优先触发一个任务,然后唤醒所有到位的线程。
CyclicBarrier是可以复用的。 他提供了一个reset的方法,在这个reset方法中,会将所有之前到位,和即将到位的线程全部唤醒结束,同时重置count计数器,清空当前CyclicBarrier,以便下次使用
CyclicBarrier是自己搞了一个count属性,每当有一个线程到位 (执行CyclicBarrier的await方法) 之后,就会对count进行--操作。等到count计数到0后,依然会唤醒,可以优先触发一个任务,然后唤醒所有到位的线程。
CyclicBarrier是可以复用的。 他提供了一个reset的方法,在这个reset方法中,会将所有之前到位,和即将到位的线程全部唤醒结束,同时重置count计数器,清空当前CyclicBarrier,以便下次使用
四、线程池的执行过程?(美团)
任务投递到线程池之后
如果当前线程池的线程个数,不满足你设定的核心线程数,那么就会创建核心线程去处理投递过来的任务。
如果线程个数等于了你设定的核心线程数,那么任务会尝试投递到工作队列中排队。
工作队列有长度,如果工作队列的长度大于排队的任务数,任务会被正常的投递到工作队列。
工作队列中的任务数和现在排队的任务数一致,任务无法投递到工作队列,此时需要创建一个非核心线程来处理刚刚投递过来的任务。
创建非核心线程时,还需要判断一下线程个数是否小于你设定的最大线程数,小于才会正常创建。
如果线程个数等于你设定的最大线程数,会执行拒绝策略。
五、为什么非核心优先执行投递的任务(美团)
首先,线程池的使用是为了提交任务给线程池,让线程池异步处理。
而在提交任务的这个过程中,其实是业务线程在执行的。
希望业务线程提交任务的过程要尽可能的短一些,让业务线程尽快的执行后续的逻辑。
如果让业务线程创建的非核心线程直接去处理提交过去的任务,速度相对是最快的一种形式。
如果让业务线程创建的非核心线程优先去拉取队列中最早投递的任务,然后业务线程再将任务投递到工作队列这种形式,就会让任务投递的过程变慢。
而在提交任务的这个过程中,其实是业务线程在执行的。
希望业务线程提交任务的过程要尽可能的短一些,让业务线程尽快的执行后续的逻辑。
如果让业务线程创建的非核心线程直接去处理提交过去的任务,速度相对是最快的一种形式。
如果让业务线程创建的非核心线程优先去拉取队列中最早投递的任务,然后业务线程再将任务投递到工作队列这种形式,就会让任务投递的过程变慢。
线程池的7个参数
核心线程可以被回收吗?
正常指定核心线程数为2个的时候,线程池即便长时间没任务,也要保留2个工作线程
但是如果allowCoreThreadTimeOut设置为true了(默认为false),那么只要工作线程超过了最大空闲时间,我就把你干掉,一个不留!
但是如果allowCoreThreadTimeOut设置为true了(默认为false),那么只要工作线程超过了最大空闲时间,我就把你干掉,一个不留!
六、(蚂蚁线程池连环问)
Java线程池,5核心、10最大、10队列,第6个任务来了是什么状态?
任务扔工作队列里~
如果在第6个任务过来的时候,5个核心线程都已经空闲了呢?
一样扔队列……线程池只关注数量!
第16个任务来了怎么处理?
创建非核心线程去处理这个第16个任务~
第16个任务来了的时候,要是有核心线程空闲了呢?
如果这个空闲的线程,将工作队列中的10个任务,取走了一个,变为了9个,那任务扔队列。
如果空闲的线程还没来得及取走任务,投递时,队列长度依然为10,那还是创建非核心。
如果空闲的线程还没来得及取走任务,投递时,队列长度依然为10,那还是创建非核心。
核心线程和非核心线程执行结束后,谁先执行队列里的任务?
谁谁空闲了,并且去等待任务,谁先去执行队列里的任务。
七、线程池参数,线程池参数怎么设置(菜鸟)
需要点到的几个信息:
你上线的服务器的硬件配置如何(你的生产环境是48G)
CPU内核数
内存大小
你线程池处理的任务情况
CPU密集
IO密集
混合型(既有IO操作、又有CPU操作)
前面的信息点清楚后,直接说你线程池中的 核心线程数 设置的是多少,以及你的 工作队列多长
核心线程数与最大线程数保持一致! 至于到底是多少,自己提前编一个数值,比如50,比如80,随你。直接聊出来。数值是你压测出来的,记住,一定是压测的,你最开始可以给一个预估的数值,但是最终结果是压测的,在你的测试环境压测,测试环境的硬件配置和生产环境一致! (有一个前提,如果你任务是混合型的,那50左右没问题,如果是CPU密集的,别太大,基本就是CPU内核数左右)
工作队列用的啥,多长。工作就常用的就俩,要么你用 ArrayBlockingQueue ,要么用 LinkedBlockingQueue ,这里我推荐大家统一 LinkedBlockingQueue 。因为你们的领导说了, LinkedBlockingQueue 底层是链表,工作队列本身就是增删比较频繁的情况,所以直接让我们用的 LinkedBlockingQueue ,效率相对更好。
至于长度:
这里你要说清楚你们这个任务触发的并发情况,如果任务体量比较大,会造成内存占用率过大。
任务的延迟时间允许的范围,如果队列太长,任务被处理时,最大的延迟时间能否接收。
队列长度要直接说是多少,一般情况就是和核心线程的2倍左右,一般情况没问题。 100长度,150长度,200长度~~
八、CopyOnWrite 怎么保证线程安全,为什么这么做?(协程)
写写互斥,写读、读写不互斥,读读也不互斥。
CopyOnWrite系列的并发集合,是基于再写入操作前,需要先获取ReentrantLock,毕竟写写操作是互斥的,然后先将本地的数据复制一份,在复制的内容中去完成写操作,在写完之后,将复制的内容覆盖掉本地的原数据。
前面的ReentrantLock,可以让写写操作直接互斥,达到线程安全的目的。
为什么还要改个副本,在副本里写的,这样内存占用率不就是double了么~~
因为还有读操作,CopyOnWrite为了提升读的性能,没有让读写之间出现互斥的操作。读和写是可以并行执行的。在有线程进行读操作时,直接读取本地的数据,写入的线程就正常的先去写到副本中。
在使用ArrayList这种线程不安全的集合时,如果需要声明到成员变量,多个线程都去访问的时候,并且读操作居多时,就应当上CopyOnWrite的系列。
九、ConcurrentHashMap在红黑树的读写并发会发生什么?
基础知识:
红黑树为了保证平衡,在写入数据时,可能会做旋转、变色的操作。
如果红黑树上的读写可以并行执行,那就造成读线程在遍历红黑树找数据时,因为写操作的旋转,从而没找到。但是数据其实是存在的,可能会有影响到你的业务。
如果真的发生了写线程正在写数据到红黑树,此时来了一个读线程,并不会让读线程阻塞等待,而是直接让读线程去双向链表(单向链表)中查询数据,虽然速度慢了一内内,但是查询会进行下去……
读线程怎么知道是否有写线程正在红黑树里写数据呢?
基于下面这个int类型的数值,作为一个锁标记
`int lockState;`
如果红黑树上的读写可以并行执行,那就造成读线程在遍历红黑树找数据时,因为写操作的旋转,从而没找到。但是数据其实是存在的,可能会有影响到你的业务。
如果真的发生了写线程正在写数据到红黑树,此时来了一个读线程,并不会让读线程阻塞等待,而是直接让读线程去双向链表(单向链表)中查询数据,虽然速度慢了一内内,但是查询会进行下去……
读线程怎么知道是否有写线程正在红黑树里写数据呢?
基于下面这个int类型的数值,作为一个锁标记
`int lockState;`
如果写线程发现有读线程正在红黑树里找数据,那写线程需要等一会,基于park挂起~~~
十、有在项目中实际使用过ConcurrentHashMap吗?哪些场景会用到?(京东健康)
ConcurrentHashMap本质就是做缓存的!将一些热点数据甩到ConcurrentHashMap里,他的速度比Redis快。毕竟你找Redis要数据,还得走一个网络IO的成本,ConcurrentHashMap就是JVM内部的数据。
比如数据已经从MySQL同步到Redis里了,但是Redis的性能不达标,或者Redis节点本身压力就比较大。那咱们就可以将缓存前置到JVM缓存中,利用ConcurrentHashMap去存储。
但是这种方式存储,如果JVM节点是集群部署,那就必然会存在不一致的问题。
* 强行走强一致,让你的缓存的存在没啥意义。。。(不这么玩)
* 通过一些中间件,MQ,Zookeeper等都可以做大监听通知或者广播的效果,这种同步可能存在延迟,达到最终一致性。
* 将一些访问量特别频繁的数据,扔到JVM内存,就生存1s甚至更少,这样可以较少对Redis的压力……同时在短时间内,也能提升性能……
类似Nacos,Eureka这种注册中心,就用到了ConcurrentHashMap,将注册中心里的注册列表的所有服务信息拉取到本地的ConcurrentHashMap中。
Spring的三级缓存用的啥??不也是ConcurrentHashMap么~~BeanDefinition
比如数据已经从MySQL同步到Redis里了,但是Redis的性能不达标,或者Redis节点本身压力就比较大。那咱们就可以将缓存前置到JVM缓存中,利用ConcurrentHashMap去存储。
但是这种方式存储,如果JVM节点是集群部署,那就必然会存在不一致的问题。
* 强行走强一致,让你的缓存的存在没啥意义。。。(不这么玩)
* 通过一些中间件,MQ,Zookeeper等都可以做大监听通知或者广播的效果,这种同步可能存在延迟,达到最终一致性。
* 将一些访问量特别频繁的数据,扔到JVM内存,就生存1s甚至更少,这样可以较少对Redis的压力……同时在短时间内,也能提升性能……
类似Nacos,Eureka这种注册中心,就用到了ConcurrentHashMap,将注册中心里的注册列表的所有服务信息拉取到本地的ConcurrentHashMap中。
Spring的三级缓存用的啥??不也是ConcurrentHashMap么~~BeanDefinition
十一、工作中的死锁怎么处理(京东健康)
互斥条件,请求保持,不可剥夺,环路………………
互斥条件(一般玩的就是互斥锁!)
每个资源只能被一个线程使用,不能被同时占用。这意味着如果有两个进程试图同时使用同一个资源,就会发生冲突。例如,如果两个进程同时尝试修改同一个文件的内容,就会导致数据混乱。互斥条件是死锁的必要条件之一,因为如果资源可以同时被多个进程使用,就不会出现死锁的情况。
请求与保持条件(一个线程在持有一个锁资源时,需要再拿另一个资源,而且之前的锁资源不释放)
一个线程需要获取新的资源才能继续执行,但已经占有的资源不能被释放。这意味着如果一个进程已经占有了某些资源,那么它还需要获取更多的资源才能继续执行。例如,如果一个进程已经占有了两个资源A和B,但它还需要一个资源C才能继续执行,而资源C已经被另一个进程占用,那么这个进程就会陷入死锁。
不剥夺条件(锁资源只能自己释放,别人不能释放!)
已经分配给进程的资源不能被强制剥夺。这意味着如果一个进程已经占有了某些资源,那么除非它自己释放,否则其他进程或系统不能强制剥夺这些资源。例如,如果一个进程已经占有了两个资源A和B,但系统强行剥夺了其中一个资源A,那么这个进程就会陷入死锁。
环路等待条件(线程1持有A资源,同时要B资源,线程2只有B资源,同时想要A资源)
多个进程形成一种头尾相接的环路,每个进程都占用了一些资源,但又都需要得到下一个未占用的资源。这意味着如果多个进程形成了一个环路,每个进程都等待下一个进程释放资源,那么这个环路上的所有进程都会陷入死锁。例如,有三个进程A、B、C,A需要资源1和资源2,B需要资源2和资源3,C需要资源3和资源1,那么A、B、C就会形成一个环路等待条件,导致所有进程陷入死锁。
互斥条件(一般玩的就是互斥锁!)
每个资源只能被一个线程使用,不能被同时占用。这意味着如果有两个进程试图同时使用同一个资源,就会发生冲突。例如,如果两个进程同时尝试修改同一个文件的内容,就会导致数据混乱。互斥条件是死锁的必要条件之一,因为如果资源可以同时被多个进程使用,就不会出现死锁的情况。
请求与保持条件(一个线程在持有一个锁资源时,需要再拿另一个资源,而且之前的锁资源不释放)
一个线程需要获取新的资源才能继续执行,但已经占有的资源不能被释放。这意味着如果一个进程已经占有了某些资源,那么它还需要获取更多的资源才能继续执行。例如,如果一个进程已经占有了两个资源A和B,但它还需要一个资源C才能继续执行,而资源C已经被另一个进程占用,那么这个进程就会陷入死锁。
不剥夺条件(锁资源只能自己释放,别人不能释放!)
已经分配给进程的资源不能被强制剥夺。这意味着如果一个进程已经占有了某些资源,那么除非它自己释放,否则其他进程或系统不能强制剥夺这些资源。例如,如果一个进程已经占有了两个资源A和B,但系统强行剥夺了其中一个资源A,那么这个进程就会陷入死锁。
环路等待条件(线程1持有A资源,同时要B资源,线程2只有B资源,同时想要A资源)
多个进程形成一种头尾相接的环路,每个进程都占用了一些资源,但又都需要得到下一个未占用的资源。这意味着如果多个进程形成了一个环路,每个进程都等待下一个进程释放资源,那么这个环路上的所有进程都会陷入死锁。例如,有三个进程A、B、C,A需要资源1和资源2,B需要资源2和资源3,C需要资源3和资源1,那么A、B、C就会形成一个环路等待条件,导致所有进程陷入死锁。
定位方式:
jstack:
arthas:
解决方式:
规避业务中出现环路等待锁的情况,这种业务的设计就存在问题。
不要走lock这种死等的方式,可以采用tryLock等待一小会,拿不到就拉到。
0 条评论
下一页