虾皮后端一面

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排队,后端系统按照自己的能力匀速处理,避免系统被突发流量冲垮。

相关推荐
我没想到原来他们都是一堆坏人3 小时前
java 动态代理
java·开发语言·动态代理
Coding_Doggy3 小时前
java面试day5 | 消息中间件、RabbitMQ、kafka、高可用机制、死信队列、消息不丢失、重复消费
java·开发语言·面试
GreatSQL社区3 小时前
GreatSQL 优化技巧:最值子查询与窗口函数相互转换
java·服务器·数据库
天生励志1233 小时前
【学习笔记】黑马Java+AI智能辅助编程视频教程,java基础入门
java·笔记·学习
这周也會开心4 小时前
Spring-MVC响应
java·spring·mvc
程序员学习随笔4 小时前
C++ 性能优化:用 CRTP 实现零开销编译期多态
java·c++
ByteBlossom4 小时前
依赖注入面试题分析—Spring面经系列(一)
java·spring
编程岁月4 小时前
java面试0106-java什么时候会出现i>i+1和i<i-1?
java·开发语言·面试
练习时长一年5 小时前
Java开发者进阶之路
java·开发语言