当前位置: 首页 > 面试经验 >

【美团】到店复活赛速通面经|讲解|0510

优质
小牛编辑
74浏览
2024-05-10

【美团】到店复活赛速通面经|讲解|0510

今天挑选一篇【美团到店复活赛面经】,给大家做讲解分析~

感谢这位同学的分享,预祝Offer多多~~~原贴链接

本文也是 《热门面经讲解》 专栏系列文章之一,大家可以点跳转链接,加个关注和订阅,我会持续更新~

自产《大厂后端Top100面试题讲解》对本篇面经题目覆盖率:9/10 = 90%

自产《大厂后端Top200面试题讲解》对本篇面经题目覆盖率:10/10 = 100%

讲解开始~~~~~

1. 讲一下Spring事务是什么,@Transactional注解如何实现

解析

属于次高频考题:属于自产《大厂后端Top200面试题讲解》

所属专项:Java|SSM框架

专项考察占比:

Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%

Java-SSM】面试考察Java时,问JavaSSM框架问题的比率:18% 参考回答

  1. 什么是事务?

事务是数据库操作的最小工作单元,它是一系列操作作为一个整体,这些操作要么全部执行,要么全部不执行,确保数据的完整性和一致性。事务具有四个特性,即原子性、一致性、隔离性和持久性,这些特性保证了事务的正确和可靠执行。

  1. Spring管理事务的方式:

Spring支持两种主要的事务管理方式:

  • 编程式事务管理:这种方式需要显式执行事务。例如,可以使用TransactionTemplate来执行事务,并需要显式调用commitrollback方法来提交或回滚事务。这种方式相对灵活,但需要开发者在代码中显式管理事务的生命周期。
  • 声明式事务管理:这是建立在AOP(面向切面编程)之上的事务管理方式。它通过对方法前后进行拦截,将事务处理的功能编织到拦截的方法中。这种方式的优势在于无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明,或者通过注解(如@Transactional)来管理事务。这种方式更符合Spring倡导的非侵入式开发方式。
  1. @Transactional注解实现:

在Spring中,@Transactional注解是实现声明式事务管理的关键。这个注解可以应用于类或方法级别,用于指定该方法或类中的所有方法都需要在事务中运行。

具体实现原理如下:

  1. 代理机制:当Spring发现一个方法或类上有@Transactional注解时,它不会直接调用这个方法,而是通过AOP(面向切面编程)技术生成一个代理对象。这个代理对象会包裹原始的目标对象,并在调用实际方法前后插入额外的逻辑,这些逻辑包括开启事务、提交事务或回滚事务等。
  2. 事务拦截器:Spring的AOP体系中有一个专门针对@Transactional注解的拦截器。在执行被注解的方法前,这个拦截器会开启一个新的数据库事务(如果当前没有事务的话)。方法执行后,拦截器会根据方法的执行情况来决定是否提交或回滚事务。
  3. 事务管理器:Spring内部使用PlatformTransactionManager接口来实现与具体的事务API交互。这个接口有多种实现,如DataSourceTransactionManagerHibernateTransactionManager等,它们分别对应不同的数据访问技术。事务管理器负责真正管理和执行事务操作。
  4. 异常检测:如果被注解的方法正常执行完毕且未抛出任何运行时异常或受检查异常,Spring会自动提交事务。如果方法抛出了未捕获的异常,Spring则会根据配置来回滚事务。

此外,@Transactional注解还支持定义事务的传播行为、超时时间、只读属性和隔离级别等,这些设置进一步增强了事务管理的灵活性和可控性。

推荐学习资料

《JavaGuide》| Java 常用框架:Spring 事务详解

2. 为什么使用Redis缓存,为什么快?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第63题》

所属专项:Redis|架构

专项考察占比:

Redis】面试中考察操作系统的比率:大厂:12%| 腾讯:7%| 阿里:13%

Redis-架构】面试考察Redis时,问“架构模型”相关问题的比率:18%

参考回答

为什么快?

  1. 基于内存操作:Redis的所有数据都存储在内存中,这使得数据的读写速度非常快。内存操作的延迟远远低于磁盘操作,因此Redis能够迅速响应客户端的请求。
  2. 简单的数据结构:Redis的数据结构是专门设计的,这些简单的数据结构的查找和操作的时间复杂度大部分都是O(1),这意味着无论数据量多大,操作的时间都是恒定的。
  3. IO多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接客户端。这样,它就可以使用一个线程连接处理多个请求,从而减少了线程切换带来的开销,并避免了IO阻塞操作。
  4. 避免上下文切换:由于Redis采用单线程模型(在早期版本中),因此可以避免不必要的上下文切换和多线程竞争。这样可以省去多线程切换带来的时间和性能上的消耗,同时单线程也不会导致死锁问题的发生。

推荐学习资料

《小林Coding》|图解Redis:Redis 线程模型

3. 如何保证Redis和MySql的数据一致性?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第73题》

所属专项:Redis|应用

专项考察占比:

Redis】面试中考察操作系统的比率:大厂:12%| 腾讯:7%| 阿里:13%

Redis-应用】面试考察Redis时,问“应用”相关问题的比率:34%

参考口述回答

先删除缓存还是先更新数据库?

  1. 先删除缓存,再更新数据库:

    1. 问题:在删除缓存后、更新数据库前的这段时间里,如果有读请求,会因为缓存已删除而读取数据库中的旧数据,然后这些数据又会被写入缓存,导致数据不一致。
  2. 先更新数据库,再删除缓存:

    1. 问题:在更新数据库后、删除缓存前的这段时间里,如果有读请求,会读取到缓存中的旧数据。此外,如果删除缓存操作失败,也会导致数据不一致。

解决方案

为了解决上述问题,我们可以采用以下策略:

  1. 延迟双删策略:

    1. 操作步骤:首先删除缓存,然后更新数据库,稍微延迟几百毫秒(这个延迟时间需要根据具体的业务场景来定),再次删除缓存。
    2. 优点:这种方法能够较大程度地保证数据库和缓存的一致性。
    3. 注意事项:延迟时间的设置是关键,太长会影响性能,太短则可能达不到预期效果。
  2. 结合消息队列和重试机制:

    1. 当更新数据库成功后,可以将删除缓存的操作放入消息队列中。
    2. 如果删除缓存失败,可以利用消息队列的重试机制来重新尝试删除操作。
    3. 优点:这种方法能够确保即使初次删除缓存失败,也能通过重试机制最终达到数据一致性。
    4. 注意事项:需要合理配置消息队列和重试策略,以避免对系统造成过大的压力。

推荐学习资料

《小林Coding》|图解Redis:数据库和缓存如何保证一致性?

4. MySQL的innodb的select方法如何加行锁?

解析

属于高频考题之一:《大厂后端Top100面试题讲解|第13题》

所属专项:MySQL|锁

专项考察占比:

MySQL】面试中考察MySQL的比率:大厂:18%| 腾讯:13%| 阿里:17%

MySQL-锁】面试考察MySQL时,问“锁”相关问题的比率:8%

参考回答

  1. select如何加行锁?

在MySQL的InnoDB存储引擎中,select语句默认是不会加锁的,它执行的是快照读,这意味着它读取的是数据的某个历史版本,而不会受到其他事务锁的影响。但是,在某些情况下,我们可能需要在读取数据时加上行锁,以确保数据的一致性。这可以通过以下方式实现:

  1. 使用SELECT ... FOR UPDATE语句

    • 当你使用SELECT ... FOR UPDATE语句时,InnoDB会对所选行加上排他锁(X锁),防止其他事务修改或删除这些行,直到当前事务结束。这种方式通常用于需要读取并随后修改数据的情况,以确保在修改前数据没有被其他事务更改。
    • 例如:START TRANSACTION; SELECT * FROM table_name WHERE id = 1 FOR UPDATE;
    • 在这个例子中,id为1的行将被加锁,直到当前事务提交或回滚。
  2. 使用SELECT ... LOCK IN SHARE MODE语句

    • SELECT ... FOR UPDATE不同,SELECT ... LOCK IN SHARE MODE会对所选行加上共享锁(S锁),允许其他事务读取这些行,但不允许修改或删除,直到当前事务结束。这种方式用于需要读取数据但不打算修改的情况,同时防止其他事务在此期间修改数据。
    • 例如:START TRANSACTION; SELECT * FROM table_name WHERE id = 1 LOCK IN SHARE MODE;
    • 在这个例子中,id为1的行将被加上共享锁,其他事务可以读取但不能修改这行数据,直到当前事务提交或回滚。
  1. 行锁的种类?

行级锁是锁定表中的某一行或某些行,从而允许其他事务并发访问表中的其他行。在MySQL中,行级锁主要包括以下几种:

  1. 记录锁(Record Locks):直接锁定索引记录,常在更新操作时使用。当某个事务正在对一条记录进行修改时,其他事务无法对该记录进行修改或删除操作。
  2. 间隙锁(Gap Locks):锁定一个范围,但不包括记录本身。这种锁主要用于防止幻读(Phantom Reads),即在一个事务读取某个范围内的记录时,另一个事务插入新的记录到这个范围内。
  3. 临键锁(Next-Key Locks):是记录锁和间隙锁的结合,锁定一个范围并包括记录本身。这种锁可以确保范围内的记录和间隙都被锁定,从而提供更严格的并发控制。

推荐学习资料

《掘金专栏》|全解MySQL数据库:# (八)MySQL锁机制

《小林Coding》|图解MySQL:# MySQL 有哪些锁?

5. 讲一下IO多路复用有哪些种类?讲一下同步非阻塞IO是什么,底层如何实现?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第59题》

所属专项:操作系统|网络系统

专项考察占比:

操作系统】面试中考察操作系统的比率:大厂:12%| 腾讯:18%| 阿里:8%

操作系统-网络系统】面试考察操作系统时,问“网络系统”相关问题的比率:22%

参考回答

IO多路复用是一种高效的IO处理方式,它允许单个进程或线程同时监视多个文件描述符,如网络连接或文件句柄。当这些描述符中的任何一个就绪时,比如有数据可读或可写,多路复用机制就能够通知应用程序进行相应的读写操作。这种机制的核心优势在于,它可以在不增加额外线程或进程的情况下,处理大量的并发连接,从而显著地提高系统的并发性和响应能力。

常见的IO多路复用技术包括select、poll和epoll等。这些技术各有特点,但核心思想都是通过一个线程来管理多个连接,减少系统资源的消耗,并提高程序运行的效率。

  1. 讲一下同步非阻塞IO是什么,底层如何实现?

同步非阻塞IO是一种IO模型,它结合了同步IO和非阻塞IO的特点。在这种模型下,用户进程发起IO请求后,会立即得到一个响应,这个响应可能是一个错误码,表示数据还没有准备好。用户进程可以继续执行其他任务,但是需要不断轮询或询问内核数据是否准备好。当数据准备好后,用户进程可以再次发起IO请求来获取数据。

关于同步非阻塞IO的底层实现,主要涉及以下几个方面:

  1. 设置Socket为非阻塞模式:同步非阻塞IO是在同步阻塞IO的基础上,通过设置Socket为非阻塞模式实现的。这意味着,在进行读写操作时,如果数据没有准备好,操作不会阻塞进程,而是立即返回一个错误码或表示数据未准备好的信息。
  2. 轮询机制:由于数据可能还没有准备好,用户进程需要不断轮询或询问内核数据是否准备好。这通常通过特定的系统调用或API实现,例如在某些系统中,可以使用select、poll或epoll等机制来监控多个文件描述符的状态变化,以便在数据准备好时进行读取或写入操作。
  3. 数据准备与获取:当内核中的数据准备好后,用户进程可以再次发起IO请求来获取数据。这时,由于数据已经就绪,因此IO操作可以顺利完成,而不会阻塞用户进程。

推荐学习资料

《小林Coding》|图解系统:IO多路复用

《小林Coding》|图解系统:阻塞与非阻塞 I/O VS 同步与异步 I/O

推荐学习资料

6. 操作系统中的内存管理如何实现?为什么现在使用段页式?段页式能够解决内部内存碎片的问题吗?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第58题》

所属专项:操作系统|内存管理

专项考察占比:

操作系统】面试中考察操作系统的比率:大厂:12%| 腾讯:18%| 阿里:8%

操作系统-内存管理】面试考察操作系统时,问“进程管理”相关问题的比率:16%

参考回答

  1. 内存管理如何实现?

虚拟内存管理是现代操作系统中非常重要的一部分,它允许程序使用比实际物理内存更大的内存空间。这种技术主要涉及到分段、分页、多级页表以及TLB(Translation Lookaside Buffer)等关键概念。

首先,我们来谈谈分段。在分段机制下,虚拟地址由两部分组成:段选择子和段内偏移量。段选择子用于指定要访问的段的起始地址和长度,而段内偏移量则表示在该段内的具体位置。操作系统会维护一个段表,记录了每个段的起始地址和长度等信息。当程序访问一个虚拟地址时,操作系统会通过段选择子从段表中找到对应的段描述符,进而计算出物理地址。

接下来是分页。分页是另一种重要的虚拟内存管理技术。操作系统会将物理内存和磁盘等辅助存储器中的空间划分为固定大小的区块,称为“页”。当程序需要更多的内存空间时,操作系统会将部分数据从物理内存中移到磁盘等辅助存储器上,而将当前需要的数据加载到物理内存中。这种机制使得程序可以透明地使用更大的内存空间,而无需关心实际物理内存的大小。

然而,随着程序规模的扩大,页表也会变得非常庞大,这导致了查找效率的问题。为了解决这个问题,我们引入了多级页表的概念。多级页表将页表进行分级,通过分级索引的方式来定位具体的页框号。这种方式可以有效地减少页表所占用的空间,并提高查找效率。

最后,我们来谈谈TLB(Translation Lookaside Buffer)。TLB是一种高速缓存结构,用于加速虚拟地址到物理地址的转换过程。它保存了最近使用的虚拟地址和物理地址之间的映射关系。当CPU发出虚拟地址时,TLB会首先检查其中是否包含所需的物理地址。如果命中,则直接返回对应的物理地址;如果未命中,则需要访问操作系统的页表以获取映射关系,并将这些信息加载到TLB中以便下次快速访问。通过这种方式,TLB可以显著提高内存访问的性能。

综上所述,虚拟内存管理通过分段、分页、多级页表和TLB等技术实现了对物理内存的抽象和扩展,为程序提供了更大的内存空间并提高了内存访问的性能。

  1. 为什么现在使用段页式?段页式能够解决内部内存碎片的问题吗?

操作系统现在使用段页式内存管理主要是为了提高内存利用率、减少外部碎片、提供更大的灵活性以及增强安全性和隔离性。同时,段页式管理也能在一定程度上解决内部内存碎片的问题。

推荐学习资料:

《小林Coding》|图解系统:虚拟内存管理总结

推荐学习资料

7. 什么是内存泄漏?内存泄露会导致什么问题?ThreadLocal内存泄漏问题?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第35题》

所属专项:Java|Java并发

专项考察占比:

Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%

Java-并发】面试考察Java时,问Java并发问题的比率:37%

参考回答

  1. 什么是内存泄漏?

内存泄漏是指在程序运行过程中,动态分配的内存没有得到及时释放,导致系统无法再对该部分内存进行回收再利用,即使这部分内存已经不再被程序所使用。简单来说,就是程序申请了内存空间后,未能适时地将其释放回操作系统,从而造成内存资源的浪费。

  1. 内存泄露会导致什么问题?
  1. 性能下降:随着内存泄漏的持续,可用内存空间会逐渐减少。这可能导致程序运行缓慢,因为系统需要频繁地进行内存分配和页面交换。
  2. 系统资源耗尽:如果内存泄漏严重且持续时间长,最终可能导致系统没有足够的内存资源分配给其他进程或程序,从而影响整个系统的稳定性和性能。
  3. 程序崩溃:当内存泄漏导致内存耗尽时,程序可能会因为无法申请到更多内存而崩溃。
  4. 系统不稳定:内存泄漏可能导致系统变得不稳定,出现各种难以预测的问题,如程序响应缓慢、界面卡顿等。
  1. ThreadLocal内存泄漏问题?

ThreadLocal如果使用不当,可能会导致内存泄漏。这主要是因为ThreadLocalMap中的Entry对ThreadLocal实例是弱引用(WeakReference),而对应的值value则是强引用。如果ThreadLocal实例被回收了,但是对应的value还没有被回收,那么这部分数据就可能成为内存泄漏的源头。

为了避免这种情况,有几种方法:

  1. 使用完ThreadLocal后,及时调用其remove()方法清除对应的Entry,避免因为线程长时间存活而导致的内存泄漏。
  2. 将ThreadLocal变量设置为static和final,以延长其生命周期,减少被垃圾回收的可能性。但这并不是根本的解决办法,因为即使ThreadLocal实例不被回收,其对应的value还是可能被长时间持有而无法释放。

因此,最佳实践是在使用完ThreadLocal后,及时通过remove()方法清除数据,以避免潜在的内存泄漏问题。

推荐学习资料

《JavaGuide》| 并发面试题:# ThreadLocal 面试题

《JavaGuide》| 详解:# ThreadLocal 详解

8. 什么是CMS垃圾回收器?有哪些新生代的垃圾回收器?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第40题》

所属专项:Java| JVM

专项考察占比:

Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%

Java-JVM】面试考察Java时,问JVM问题的比率:24%

参考回答

什么是CMS垃圾回收器?

CMS垃圾回收器,全称为Concurrent Mark-Sweep,它是一款针对Java虚拟机的老年代设计的垃圾回收器。其特点主要在于并发性,即在垃圾回收的过程中,应用程序可以继续运行,从而极大地减少了停顿时间,非常适合对响应时间有严格要求的应用。不过,CMS垃圾回收器可能会导致内存碎片化问题。

新生代的垃圾回收器有哪些?

针对新生代的垃圾回收器,主要有以下几种:

  1. Serial收集器:这是最基础、最简单的垃圾回收器,采用复制算法,单线程执行。它适用于小内存环境,但在进行垃圾回收时,必须暂停其他所有的工作线程,直到回收结束。
  2. ParNew收集器:这是Serial收集器的多线程版本,也采用复制算法。它利用多个线程进行垃圾回收,适合多CPU的环境。
  3. Parallel Scavenge收集器:这款新生代收集器也是基于标记复制算法,并能够并行收集。与CMS等收集器不同,它更注重吞吐量,即处理速度,而不是尽可能地缩短垃圾收集时用户线程的暂停时间。

推荐学习资料

《JavaGuide》| JVM面试题:#垃圾收集器

9. Linux命令,如何查看某一个进程?如何查看进程的线程?如何使用Linux命令查看jvm线程?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第61题》

所属专项:操作系统|Linux命令

专项考察占比:

操作系统】面试中考察操作系统的比率:大厂:12%| 腾讯:18%| 阿里:8%

操作系统-Linux命令】面试考察操作系统时,问“网络系统”相关问题的比率:7%

参考回答

2.如何查看某个进程线程?

在Linux中,查看某一个进程或进程的线程,我们通常会使用ps命令或者tophtop这样的工具。

首先,要查看某一个进程,你可以使用ps命令配合grep来过滤出你感兴趣的进程。比如,如果你想查找名为"nginx"的进程,你可以执行:ps aux | grep nginx

这里,ps aux会显示所有用户的所有进程,并通过grep来过滤出包含"nginx"的行。

另外,如果你想查看更详细的进程信息,可以使用ps -ef,这样会显示进程的父进程ID、进程ID、启动命令等详细信息。同样,你也可以配合grep来查找特定进程。

至于查看进程的线程,你可以使用ps -eLf命令,这个命令会显示所有进程的线程信息。如果你想查看特定进程的线程,可以结合grep使用。例如,如果你想查看PID为1234的进程的线程,可以执行:ps -eLf | grep 1234

这样,你就可以看到PID为1234的进程下的所有线程信息了。

除了ps命令,你还可以使用tophtop这样的实时监控系统工具来查看进程和线程。在top中,你可以按H键来切换是否显示线程,从而查看某个进程的线程信息。而htop则提供了一个更友好的界面来查看和管理进程及线程。

总的来说,Linux提供了多种方法来查看和管理进程及线程,你可以根据自己的需要选择合适的方法。

2.如何使用Linux命令查看JVM线程?

查看JVM线程通常涉及到对Java进程的监控。在Linux中,你可以使用以下方法来查看JVM线程:

  1. 使用jstack命令:

jstack是Java提供的一种堆栈跟踪工具,可以用于打印出Java线程堆栈信息。使用方法如下:jstack -l PID

其中,PID是Java进程的进程ID。这个命令会打印出详细的线程堆栈信息,包括线程名、线程状态、调用堆栈等。

  1. 使用jconsole命令:

jconsole是一个Java监视和管理控制台,可以用于监控Java应用程序的性能和内存使用情况。通过jconsole,你可以连接到正在运行的Java进程,并查看其中的线程信息。

推荐学习资料

《小林Coding》|公众号:2万字系统总结,带你实现 Linux 命令自由

10. 讲一下HashMap?

解析

属于高频考题:自产《大厂后端Top100面试题讲解|第22题》

所属专项:Java|Java集合框架

专项考察占比:

Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%

Java-集合框架】面试考察Java时,问Java集合框架问题的比率:9%

参考口述回答

一、数据结构

  • HashMap内部使用了一个数组加链表和红黑树的组合。数组用于存储键值对的桶(bucket),而链表和红黑树则用于解决哈希冲突。
  • 当发生哈希冲突时,HashMap会在数组的同一个桶中形成链表。为了提升性能,在Java 8中,当链表长度超过一定阈值(默认为8)时,链表会转化为红黑树。

二、哈希计算与桶定位

  • HashMap使用哈希函数来计算键的哈希值,并根据这个哈希值确定键值对应该存放在数组的哪个桶中。
  • 这个哈希函数被设计为尽量减少冲突,并确保键值对能够均匀分布到各个桶中。

三、冲突解决与数据结构转换

  • 当插入新的键值对时,如果计算出的桶已经有元素存在(即发生哈希冲突),则新的键值对会被添加到该桶对应的链表中。
  • 如果链表长度过长(超过8),并且HashMap的数组长度大于或等于64,则链表会转换为红黑树,以提高搜索效率。

四、扩容机制

  • HashMap内部有一个扩容机制,当元素数量达到数组长度与加载因子(默认为0.75)的乘积时,就会触发扩容操作。
  • 扩容时,HashMap的容量会翻倍,并且所有的元素都需要重新散列到新的数组中。这个过程是为了保持哈希表的均匀分布和减少冲突。

五、线程安全

  • HashMap并不是线程安全的。如果在多线程环境下使用HashMap,可能会导致数据不一致的问题。如果需要线程安全的哈希表,可以考虑使用ConcurrentHashMap。

推荐学习资料

《JavaGuide》| 集合面试题:# HashMap底层如何实现的?

最后

本文也是 《热门面经讲解》 专栏系列文章之一,大家可以点跳转链接,加个关注和订阅,我会持续更新~

#面经##美团##实习##秋招##24届软开秋招面试经验大赏#
 类似资料: