面试总结:没有那种纯八股问题,都是偏向于情景题。看到面试官最后出了一道多叉树的题目,我以为是想直接刷人,但还是尽力去尝试了一下,最后也没做出来,面试官很nice,在答不上来的时候会引导我去思考,并在我回答正确的时候给与充分的肯定。
线程池的工作过程主要包括以下几个步骤:
(1) 线程池创建时会初始化一定数量的核心线程,等待任务。
(2) 当有新任务提交时,会首先判断当前运行的线程数是否小于核心线程数。如果小于,则创建新的线程来执行任务。
(3) 如果当前运行的线程数大于或等于核心线程数,则将任务放入队列中等待执行。
(4) 如果队列已满,且当前线程数小于最大线程数,则创建新的线程来执行任务。
(5) 如果队列已满,且当前线程数等于最大线程数,则执行拒绝策略。
(6) 当线程完成任务时,会从队列中取出新的任务来执行。
(7) 当线程空闲时间超过keepAliveTime,如果当前运行的线程数大于核心线程数,则这些线程会被停止。
线程池的线程回收机制主要依赖于线程池的工作队列和keepAliveTime参数:
(1) 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直到线程池中的线程数不超过corePoolSize。
(2) 如果设置了allowCoreThreadTimeOut(true),则核心线程在空闲时间超过keepAliveTime后也会被回收。
(3) 线程池使用一个HashSet存储工作线程的引用,在工作线程退出时,它会被从线程池中移除。
(4) 在ThreadPoolExecutor的getTask()方法中,如果线程获取任务时超时,说明该线程可以被回收。
(5) 线程回收的过程是通过线程自己执行完run方法来实现的,而不是由外部强制中断。
这种机制可以在执行大量短期异步任务时,提高线程的复用性,避免频繁创建和销毁线程带来的开销。
区分核心线程和非核心线程主要有以下几个原因:
(1) 资源管理:核心线程代表了线程池在空闲时刻的最小线程数,可以保证线程池的快速响应能力。
(2) 性能优化:核心线程通常不会被回收,可以减少线程创建和销毁的开销。
(3) 弹性伸缩:通过调整核心线程数和最大线程数,可以实现线程池的动态伸缩,适应不同的负载情况。
(4) 任务优先级:核心线程可以优先处理任务,非核心线程则在任务量增大时才会被创建。
(5) 资源控制:可以通过控制核心线程数来限制系统资源的使用,避免创建过多线程导致系统负载过高。
这种设计使得线程池可以在保证基本性能的同时,根据实际负载情况动态调整,既保证了响应速度,又避免了资源浪费。
数据库查询优化是一个复杂的话题,主要包括以下几个方面:
(1) 索引优化:
(2) SQL语句优化:
(3) 表结构优化:
(4) 架构优化:
在实际优化中,需要根据具体的业务场景和查询模式来选择合适的优化策略。
(1) 主从复制机制:
(2) 数据一致性问题:
(3) 故障转移:
(4) 读写分离:
(5) 数据过期问题:
(6) 数据持久化:
在实际应用中,需要根据业务需求和系统规模来设计合适的Redis集群架构。
缓存优化的考量
(1) 缓存预热:
(2) 缓存更新策略:
(3) 缓存穿透优化:
(4) 缓存击穿优化:
(5) 缓存雪崩优化:
(6) 缓存降级:
针对缓存优化的细节,可以从以下几个方面深入讨论:
(1) 缓存key的设计:
(2) 缓存粒度:
(3) 缓存有效期:
(4) 缓存预加载:
(5) 缓存更新机制:
(6) 缓存数据一致性:
Canal
等工具实现数据库和缓存的同步这些细节需要在实际应用中不断调优和验证,以达到最佳的性能和可靠性。
设计一个消息队列系统需要考虑很多方面,以下是一些关键点:
(1) 消息存储:
RocksDB
或自研的 LSM
树存储引擎Log Segment
)存储方式,便于清理过期数据Memory Mapped File
)提高I/O性能(2) 高可用性:
Raft
或 Paxos
等一致性协议,实现leader选举Failover
)机制(3) 高吞吐量:
Zero-Copy
)技术减少数据复制(4) 消息可靠性:
ACK
)机制(5) 扩展性:
Partition
)机制,支持横向扩展(6) 消息顺序性:
(7) 消息模型:
(8) 客户端SDK:
实际情况还需要根据具体需求和场景进行取舍和优化。
(1) 异步处理:
(2) 流量削峰:
(3) 解耦系统:
(4) 扩展性:
(5) 可靠性投递:
(6) 顺序保证:
(7) 流量控制:
(8) 事务消息:
(1) 缓存:
(2) 计数器:
(3) 分布式锁:
(4) 会话存储:
(5) 排行榜:
(6) 消息队列:
(7) 分布式ID生成:
(8) 布隆过滤器:
(9) 延迟队列:
Redis
实现分布式锁的基本步骤如下:
(1) 获取锁: 使用 SETNX
命令尝试设置一个键值对,如果键不存在则设置成功并获得锁:
(2) 设置过期时间: 为了防止客户端崩溃导致锁无法释放,需要设置过期时间
(3) 执行业务逻辑
(4) 释放锁: 使用 Lua
脚本保证原子性,只有持有锁的客户端才能释放锁:
注意事项:
Redisson
等成熟的实现,它们提供了自动续期等高级特性这种实现方式适用于简单场景,但在高并发或对可靠性要求较高的场景下,建议使用Redisson等专业的分布式锁实现。
Redisson 是一个在 Java 客户端上使用 Redis 的工具库。它提供了一个易于使用且强大的一套 API,来简化分布式系统的开发。Redisson 提供了多种分布式锁机制,比如公平锁、读写锁、红锁等,适合各种不同的场景,帮助开发者解决并发控制问题。
redis 锁的局限性
DEL
命令在分布式系统中可能会删除不是自己设置的锁。Redisson 锁
java.util.concurrent.locks.Lock
接口的抽象,让使用锁变得更加自然和简单。Redisson 锁的优势
(1) 是否会产生死锁
Redisson 锁在设计上考虑到避免死锁。它通过设置锁的过期时间和自动续期机制来防止持有锁的节点意外崩溃或长时间失去响应从而导致死锁。
(2) Redisson 锁如何实现
SET
命令加上 NX
(不存在时设置)和 PX
(过期时间)参数来实现原子操作,从而确保只有一个客户端能够成功获得锁。(3) 过期时间的作用及默认值
过期时间的作用是防止在锁持有者发生意外崩溃或长时间失去响应时,锁一直存在而导致其他客户端无法获得锁。过期时间确保了锁最终会在一段时间后自动释放,从而避免死锁。
Redisson 默认的锁过期时间是 30 秒。这意味着如果没有特殊配置,Redisson 锁在持有之后,如果不进行操作,30秒后会自动释放。
(4) 自动续期机制
Redisson 的自动续期机制依靠一个叫做 Watchdog(看门狗)的后台线程来实现。当一个客户端成功获取到锁时,一个后台的 Watchdog 线程会启动,默认每隔10秒执行一次续期操作,将锁的过期时间延长一定周期,以此确保持有锁的客户端在执行长时间任务时不会因为锁的过期而失去锁。
我还将定期分享:
最新互联网资讯:让你时刻掌握行业动态。
AI前沿新闻:紧跟技术潮流,不断提升自我。
技术面试与职业发展:助你在职业生涯中走得更远、更稳。
程序员生活趣事:让你在忙碌的工作之余找到共鸣与乐趣。
关注回复【1024】惊喜等你来拿!