拼多多客户端研发一面面经
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- 实习项目是做什么的,介绍一下?
- 这个地图渲染主要用的什么技术?前端 JS 做地图渲染的技术有去了解吗?
- 你这边还做了哪些工作?有哪些觉得有意思的功能值得分享的?
- 你开发这个功能用的什么设计模式?
- 实验室项目里你主要工作是什么?有用到一些框架吗?
- Kotlin 的协程有用过吗?
- 蓝牙用的什么协议?蓝牙协议的知识有了解吗?
- MQTT 是你选型和搭建的吗?MQTT 主要用在什么场景?有什么特点?
- 为什么选择做这个个人项目?这里面有什么技术亮点吗?
- AI 是放在服务端的吗?客户端怎么和服务端交互的?
- 并发和并行有什么区别?
- 进程和线程有什么区别?
- TCP 三次握手的过程是怎样的?两次握手会怎么样?
- HTTP1.0 的长连接复用和 HTTP2.0 的多路复用有什么区别?
- Java 怎么实现同步的?
- synchronized 关键字写在方法前面和方法里面有什么区别?
- synchronized 关键字写在 static 静态方法前面会怎样?
- 手写代码:如何实现一个 LFU Cache?
- 手写代码:整数数组求最大的连续区间和?
《参考解析》
并发与并行、进程与线程,这两组概念要一次说清。 并发是「同时应对多件事」的能力,单核靠时间片轮转也能并发;并行是「同一时刻真正同时执行」,必须有多个物理核心。进程是资源分配的基本单位,拥有独立地址空间,隔离性强但创建和切换昂贵(页表、TLB、上下文);线程是 CPU 调度的基本单位,同一进程内的线程共享内存和文件描述符,只需要保存寄存器和栈,切换便宜、通信方便,代价是要自己处理同步问题。再补一句 Java 侧的现状:现代 JVM 的线程和 OS 线程是一对一映射,所以「几万个线程」是不现实的;真正的用户态并发要靠虚拟线程或者业务层的异步 IO 框架。
TCP 三次握手为什么不能少一次。 流程是客户端发 SYN(带上自己的初始序列号)→ 服务端回 SYN+ACK(带上自己的初始序列号并确认对方)→ 客户端回 ACK 确认。第三次的意义是让服务端确认「客户端确实收到了我的 SYN+ACK」——只握两次的话,服务端发完 SYN+ACK 就认为连接已建立并分配资源,但客户端可能压根没收到这条报文。更实际的危害是历史重复报文:网络里一个延迟很久的旧 SYN 到达服务端,两次握手下服务端会直接建立一个半开连接并占着资源,而客户端根本不认这条连接,这就是 SYN Flood 的资源消耗原理。四次是能省成三次的,因为服务端的 SYN 和 ACK 可以合并成一条发。
HTTP1.0 的长连接和 HTTP2.0 的多路复用不是一回事。 HTTP1.0 默认一个请求一个 TCP 连接,加了 Connection: keep-alive 之后连接可以复用,但同一个连接上同一时刻仍然只能跑一个请求,后发的请求必须等前一个响应回来——这是 HTTP 层的队头阻塞,浏览器只能靠对同一域名开 6 个左右并发连接来绕过。HTTP2.0 把请求和响应拆成带 stream id 的二进制帧,在一条 TCP 连接上交错发送、互不阻塞,真正做到一个连接并发多路;同时加了 HPACK 头部压缩和服务端推送。但 HTTP2.0 仍然跑在 TCP 上,丢一个包会阻塞这条连接上所有的流,所以才有基于 UDP 的 QUIC / HTTP3 来解决传输层队头阻塞。
synchronized 的差异本质是「锁的是谁」。 修饰实例方法时锁是 this,也就是当前实例对象——同一个类的不同实例各有一把锁,互不阻塞,如果业务上要保护的是跨实例的静态资源,这么写就是错的。修饰代码块时锁是括号里那个对象,粒度可以自己控制,实践中应该选一个不会被外部共用的私有对象,别用 String 常量或者包装类缓存对象当锁,否则别的代码可能拿到同一把锁产生意外竞争。修饰静态方法时锁是类的 Class 对象,全局唯一,等价于把整个类的调用串行化,同一个类的所有实例都来抢这一把锁。另外要点出 synchronized 是可重入的、非公平的,JDK6 之后有偏向锁到轻量级锁再到重量级锁的升级过程,简单场景下不比 ReentrantLock 差;需要超时、可中断、公平锁或多条件队列时才换 ReentrantLock。
LFU Cache 要做到 O(1),靠的是「哈希 + 频次桶 + 双向链表」三件事。 用一个 map<key, node> 做定位,node 里存 value、freq 以及前后指针;再用 map<freq, 双向链表> 把同一频次的节点串起来。get 命中后要把节点从原频次链表摘下、插入 freq+1 的链表头部,并维护全局的 minFreq。put 时若已满,就淘汰 minFreq 那条链表的尾部节点(同频次内部按最近使用排序,尾部就是最久未用的,所以 LFU 天然要叠一层 LRU 来打破平局),新节点放到 freq=1,minFreq 置为 1。面试里更出彩的是接着讲 LFU 的固有缺陷:历史热点会靠高 freq 长期占位,新数据几乎进不来;工程上要么做频次衰减,要么直接用 W-TinyLFU 那种「频率统计 + LRU 窗口 + 频率门控」的混合策略,Caffeine 就是这么做的。