虾皮后端一面

1.介绍一下hashMap

hashMap是基于哈希表的Map接口实现,它使用键值对(key-value)存储数据,允许使用null键和null值;

核心原理与特点:

1.数据结构:JDK1.8之后,底层是"数组+链表+红黑树".数组是主题,链表是为了解决哈希冲突(即不同的key计算出相同的值),当链表长度超过阈值8的时候且数组长度大于64时,链表会转化为红黑树,以提高查询效率;

2.哈希函数:通过key的hashCode()的值进行高位运算和取模运算,得到数组下标,目标是分布均匀;

3.重要参数:

  • 容量(Capacity):数组的长度,默认是16.

  • 负载因子(Load Factor) : 默认是0.75

4.线程安全:HashMap是线程非安全的;

2.HashMap多线程安全么,怎么实现多线程安全?

线程不安全.在多线程环境下,对HashMap进行put操作可能导致死循环(JDK1.7)之前,数据丢失或数据覆盖问题.

实现线程安全的方法:

1.使用Collections.synchronizedMap(Map):他会返回一个包装后的Map,所有方法都用synchronized关键字加锁,性能较差.

2.使用ConcurrentHashMap(推荐):这是专门为高并发设计的线程安全的HashMap.

  • JDK1.7:采用分段锁技术,将数据分成一段一段存储,每段配一把锁,允许多线程同时访问不同段的数据,提高并发度;

  • JDK1.8之后:摒弃了分段锁,改用synchronized+CAS(Compare And Swap)操作来实现线程安全.锁的粒度更细,只锁住数组的单个桶(链表或者红黑树的头结点),并发性能较高.

3. 说说为什么Redis使用这么广泛

Redis的广泛使用主要源于其卓越的性能和丰富的数据结构:

1.数据存储:Redis数据主要存储在内存中,读写性能极快;

2.丰富的数据结构: 不仅支持简单的String,还支持List,Hash,Set,Sorted Set,BitMap等,可以实现复杂业务(排行榜,好有关系等);

3.持久化机制:虽然基于内存存储,但是提供了RDB(快照)和AOF(日志)两种持久化机制,保证数据不丢失;

4.高可用和分布式:通过Redis Sentinel(哨兵)实现高可用,通过Redis Cluster集群实现数据分片和横向扩展.

5.功能丰富:支持发布订阅功能,事务,Lua脚本和过期键等功能;

4.介绍一下JMM

JMM (Java Memory Model,Java内存模型) 是一种抽象概念,它定义了Java程序中的各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量的底层细节.

JMM的核心目标是解决多线程通信中的原子性,可见性,有序性问题.

  • 主内存:存储所有共享变量。

  • 工作内存:每个线程都有自己的工作内存,存储该线程使用到的共享变量的副本。

JMM规定了以下操作:

  1. 线程对变量的所有操作(读、写)都必须在工作内存中进行。

  2. 不同线程之间不能直接访问对方工作内存中的变量。

  3. 线程间变量值的传递需要通过主内存来完成。

关键字(如synchronizedvolatile)以及java.util.concurrent包下的工具,都是JMM规则的具体实现,它们保证了多线程环境下的内存可见性、操作原子性和禁止指令重排序。

5.线程池一般如何使用?

在实际项目中,不建议使用Executors工具类直接创建线程池 (如newFixedThreadPoolnewCachedThreadPool),因为它们内部使用的阻塞队列可能无限长(如LinkedBlockingQueue),容易导致OOM(内存溢出)。

推荐使用ThreadPoolExecutor构造函数手动创建,以便更精准地控制参数:

java

复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,     // 核心线程数(如:CPU核数+1)
    maximumPoolSize,  // 最大线程数(如:CPU核数 * 2)
    keepAliveTime,    // 非核心线程空闲存活时间
    TimeUnit.SECONDS, // 时间单位
    new LinkedBlockingQueue<>(capacity), // 有界阻塞队列,需指定大小
    new CustomThreadFactory(), // 自定义线程工厂,便于日志追踪
    new CustomRejectedExecutionHandler() // 自定义拒绝策略
);

根据任务类型(CPU密集型、IO密集型)来设置核心参数。

6. 线程池中的拒绝策略你了解么?

当线程池的线程数达到最大值且工作队列已满时,对新提交的任务会触发拒绝策略。JDK内置了四种:

  1. **AbortPolicy(默认)**:直接抛出RejectedExecutionException异常。

  2. **CallerRunsPolicy**:将任务回退给调用者线程(即提交任务的main线程或其它线程)来执行。

  3. **DiscardPolicy**:直接丢弃新任务,不做任何处理。

  4. **DiscardOldestPolicy**:丢弃队列中最老的一个任务,然后尝试再次提交当前任务。

也可以实现RejectedExecutionHandler接口来自定义拒绝策略,如将任务持久化到磁盘或记录日志后重试。

7.线程池中来了一个线程,阻塞队列慢了,最大线程没满,会怎么样?

这个问题的描述可能有点歧义,更精确的问法是:"当一个新的任务交给线程池时,如果当前运行的线程数小于核心线程数,会直接创建新的线程.如果等于核心线程数,且工作队列还没满时,任务会入队等待.如果工作队列已满,但当前线程数小于最大线程数,会怎么样?"

答案是:线程池会创建新的非核心线程来立即执行这个新提交的任务,而不是将其放入已满的队列中.

8.如何使用RabbitMQ实现订单超时取消功能?

利用RabbitMQ的消息TTL和死信队列(DLX).

实现步骤:

  1. 创建业务队列(order.delay.queue

    • 为此队列设置TTL(例如30分钟),并指定一个死信交换机(order.dlx.exchange)作为其死信交换机。
  2. 创建死信交换机(order.dlx.exchange)和死信队列(order.cancel.queue :并将它们绑定,路由键为order.cancel

  3. 下单时 :用户下单后,向业务队列(order.delay.queue)发送一条消息,该消息在30分钟后会自动过期。

  4. 消息过期 :30分钟后,过期的消息会被自动路由到死信交换机(order.dlx.exchange),再被路由到死信队列(order.cancel.queue)。

  5. 消费取消逻辑 :有一个消费者监听死信队列(order.cancel.queue)。一旦收到消息,它就检查订单状态:

    • 如果订单仍是"未支付"状态,则执行取消订单、释放库存的逻辑。

    • 如果订单已支付,则直接丢弃消息,不做任何操作。

优点:避免了轮询数据库,性能高,解耦性好。

9. 为什么使用rabbitMQ

使用RabbitMQ(消息队列)的核心目的是解耦、异步、削峰填谷

  1. 解耦:订单系统生成订单后,只需要发送一个消息到MQ,而不需要直接调用库存系统、积分系统等。即使下游系统宕机或需要扩展,也不会影响订单系统。

  2. 异步:主流程(如下单)可以快速响应给用户,而耗时的下游操作(如发短信、更新积分)通过MQ异步处理,提升系统整体响应速度。

  3. 削峰填谷:在秒杀等瞬时高并发场景下,请求可以先涌入MQ排队,后端系统按照自己的能力匀速处理,避免系统被突发流量冲垮。

相关推荐
Zoey的笔记本9 分钟前
「支持ISO27001的GTD协作平台」数据生命周期管理方案与加密通信协议
java·前端·数据库
lpfasd12315 分钟前
Spring Boot 4.0.1 时变更清单
java·spring boot·后端
N***H48621 分钟前
SpringBoot3.3.0集成Knife4j4.5.0实战
java
程序员欣宸1 小时前
LangChain4j实战之十三:函数调用,低级API版本
java·人工智能·ai·langchain4j
Java新手村1 小时前
【订单超时取消怎么设计】
java
阿蒙Amon2 小时前
C#每日面试题-常量和只读变量的区别
java·面试·c#
寻星探路2 小时前
【算法专题】滑动窗口:从“无重复字符”到“字母异位词”的深度剖析
java·开发语言·c++·人工智能·python·算法·ai
程序员小白条2 小时前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
萤丰信息2 小时前
从 “钢筋水泥” 到 “数字神经元”:北京 AI 原点社区重构城市进化新逻辑
java·大数据·人工智能·安全·重构·智慧城市·智慧园区
week_泽3 小时前
第5课:短期记忆与长期记忆原理 - 学习笔记_5
java·笔记·学习·ai agent