编辑
2023-11-16
面试题库
0

线程池的运行处理流程主要可以分为以下几个步骤:

1、任务提交: 当一个任务被提交给线程池时,线程池首先会判断核心线程池(corePoolSize)是否已满。如果核心线程池未满,线程池会创建一个新的工作线程来执行任务。如果核心线程池已满,进入下一步。

2、任务队列: 如果核心线程池已满,新提交的任务会被放入工作队列(workQueue)中。如果工作队列已满,进入下一步。

3、创建额外线程: 如果工作队列已满,且当前线程总数小于最大线程数(maximumPoolSize),线程池会创建新的线程来处理被添加到工作队列中的任务。如果当前线程总数已达到最大线程数,进入下一步。

4、拒绝策略: 如果当前线程总数已达到最大线程数,且工作队列也已满,线程池会采用预定义的拒绝策略(RejectedExecutionHandler)来处理无法执行的任务。

编辑
2023-11-12
面试题库
0

可以从以下几个角度来分析:

1、任务的性质:CPU密集型任务、IO密集型任务和混合型任务。

2、任务的优先级:高、中和低。

3、任务的执行时间:长、中和短。

4、任务的依赖性:是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置 N cpu + 1 个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2 * N cpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量 将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过 Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。 优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

编辑
2023-11-11
面试题库
0

在Java中,HashMap是一种用于存储Key-Value键值对的数据结构,它在内存中的存储实际上是一个数组结构,数组的每一项称为一个桶,每一个桶中可以存放一个链表,链表的每一项都包含了一个Key-Value键值对。

正因为此,HashMap能够以常数复杂度(O(1))进行数据的查找和插入操作,但也正因为此,HashMap也存在着“冲突”的问题。

在JDK1.8之前,当冲突发生时,HashMap使用链表来解决冲突,但是在多次冲突的情况下,HashMap的查找性能会降低到O(n),这导致在元素较多时HashMap的效率会大大降低。

而在JDK1.8之后,为了优化多次冲突的情况,HashMap采用了优化后的链表,也就是红黑树,当链表长度大于一定数量(默认为8)时,链表就转为红黑树,这样即使多次冲突,查找性能也可以保持在O(logn)。

编辑
2023-11-05
缓存中间件
0

1、缓存延时双删

​ 先删除缓存,再更新数据库,休眠一会(比如1秒),再次删除缓存。这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。而且这个休眠时间在某些业务环境下不太好拿捏。这种方案还算可以,只有休眠那一会(比如就那1秒),可能有脏数据,一般业务也会接受的。但是如果第二次删除缓存失败呢?缓存和数据库的数据还是可能不一致。给Key设置一个自然的expire过期时间,让它自动过期怎样?那业务要接受过期时间内,数据的不一致。

2、删除缓存重试机制

延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题。可以使用这个方案优化:删除失败就多删除几次,保证删除缓存成功就可以了, 所以可以引入删除缓存重试机制。先写请求更新数据库,缓存因为某些原因,删除失败,把删除失败的key放到消息队列,消费消息队列的消息,获取要删除的key,重试删除缓存操作。

3、读取biglog异步删除缓存

​ 重试删除缓存机制还不粗,但是会造成很多业务代码入侵。其实,还可以这样优化:通过数据库的binlog来异步淘汰key。以mysql为例:可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性。

想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

​ 引入缓存后,需要考虑缓存和数据库一致性问题,可选的方案有:「更新数据库 + 更新缓存」、「更新数据库 + 删除缓存」。

​ 更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,解决方案是加「分布锁」,但这种方案存在「缓存资源浪费」和「机器性能浪费」的情况。

​ 采用「先删除缓存,再更新数据库」方案,在「并发」场景下依旧有不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估。

​ 采用「先更新数据库,再删除缓存」方案,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据最终一致。

​ 采用「先更新数据库,再删除缓存」方案,「读写分离 + 主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率。

编辑
2023-11-04
缓存中间件
0

有哪些方案?

1、setnx + expire

但是setnx和expire分2步执行,非原子操作;若setnx执行成功,但expire执行失败,就可能出现死锁。而且不支持阻塞等待、不可重入。还有一个问题,如果业务逻辑执行时间超过锁的过期时间,没有办法续约,其他线程可以抢到锁,导致并发问题。

2、Redisson实现分布式锁

说说红锁(RedLock)

RedLock算法

​ RedLock:基于Redis实现的分布式锁,在分布式环境下必须保证不同的进程是以互斥的方式使用共享资源,具体的实现根据编程语言有很多,Java中是Redisson。

加锁:加锁实际上就是redis中,给key设置一个值,为避免死锁,还需要给定一个超时时间。

解锁:将key删除,但是前提是只能自己删除自己的锁,而且需要保证删锁的原子性。

超时:key要有过期时间,不能长时间占用。

​ Redis加锁只作用在一个Redis节点上,无论通过哨兵或者主从保证高可用,当master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:

​ 1、客户端1在Redis的master节点上拿到了锁

​ 2、Master宕机了,存储锁的key还没有来得及同步到Slave上

​ 3、master故障,发生故障转移,slave节点升级为master节点

​ 4、客户端2从新的Master获取到了对应同一个资源的锁

 于是,客户端1和客户端2同时持有了同一个资源的锁,锁的安全性被打破了,RedLock正是用来解决这个问题的方案: