2021-01-19 22:37:05
拼多多社招Java一面主要围绕操作系统、数据库、并发编程、缓存及高并发高可用设计等核心知识点展开,并包含一道算法编程题。具体面试问题及解答要点如下:
操作系统相关线程和进程的区别
资源占用:进程是资源分配的基本单位,拥有独立的内存空间、文件描述符等;线程是CPU调度的基本单位,共享进程的内存空间(如代码段、数据段等),但拥有独立的栈和程序计数器。
开销:进程创建、销毁和切换的开销较大(需申请/释放资源、更新PCB等);线程开销较小(共享进程资源,切换仅需保存/恢复少量寄存器状态)。
通信:进程间通信(IPC)需通过管道、消息队列、共享内存等机制;线程间可直接通过共享内存通信,但需同步机制避免数据竞争。
线程和进程在调度时的区别
调度单位:进程是资源分配单位,线程是CPU调度单位。操作系统调度时,优先调度线程(轻量级),进程内线程共享进程时间片。
上下文切换:进程切换需保存/恢复完整的进程上下文(内存映射、文件状态等);线程切换仅需保存/恢复线程私有数据(栈、寄存器等),开销更小。
操作系统给线程和进程分配的资源
进程资源:独立地址空间、全局变量、文件描述符、信号处理、子进程控制权等。
线程资源:线程栈(存储局部变量、函数调用栈)、程序计数器、寄存器组、线程本地存储(TLS)等。
线程安全及实现方式
定义:当多个线程访问共享数据时,若能保证数据在任何时刻的修改都是正确的(如原子性、可见性、有序性),则称线程安全。
实现方式:
同步机制:使用synchronized关键字、ReentrantLock等互斥锁保证临界区代码的原子性。
不可变对象:通过final修饰共享变量或使用不可变类(如String)避免修改。
线程局部存储:使用ThreadLocal为每个线程分配独立变量副本。
并发容器:使用ConcurrentHashMap、CopyOnWriteArrayList等线程安全集合。
互斥锁的实现原理
硬件支持:依赖CPU提供的原子指令(如CAS,Compare-And-Swap)或操作系统提供的信号量机制。
Java实现:synchronized通过JVM的monitorenter和monitorexit指令实现,底层依赖操作系统的互斥锁(如Linux的futex);ReentrantLock通过AbstractQueuedSynchronizer(AQS)框架实现,使用CAS操作和队列管理线程阻塞与唤醒。
ReentrantLock的灵活性及实现
灵活性:
支持公平锁(先到先得)和非公平锁(可插队),通过构造函数参数配置。
提供tryLock()方法,支持超时或非阻塞获取锁。
支持可中断锁(lockInterruptibly()),允许线程在等待锁时响应中断。
实现:
lock():通过CAS操作尝试获取锁,若失败则将线程加入同步队列并阻塞。
unlock():释放锁并唤醒队列中的后续线程(通过LockSupport.unpark())。
MySQL索引的数据结构
B+树:MySQL默认使用B+树作为索引结构,因其具有以下优势:
平衡性:所有叶子节点在同一层,保证查询效率稳定(O(log n))。
范围查询:叶子节点通过指针连接,支持高效的范围扫描。
高扇出性:非叶子节点仅存储键值,可容纳更多索引项,减少树高度。
多条索引下的查询策略
索引合并:MySQL可能使用多个单列索引进行合并(如index_merge优化),通过OR或AND条件组合结果。
最左前缀原则:对于复合索引(如(a, b, c)),查询需从左到右匹配连续列(如a=1 AND b=2可用索引,b=2不可用)。
索引选择性
定义:索引列中不同值的数量与总记录数的比值(范围0~1),选择性越高(接近1),索引区分度越好,查询效率越高。
优化:为高选择性列(如用户ID、订单号)创建索引,避免低选择性列(如性别、状态)单独建索引。
缓存雪崩及解决方案
定义:大量缓存同时失效或缓存服务不可用,导致请求直接打入数据库,引发系统崩溃。
解决方案:
随机过期时间:为缓存键设置随机过期时间,避免集中失效。
多级缓存:使用本地缓存(如Caffeine)和分布式缓存(如Redis)分层存储。
熔断降级:当数据库压力过大时,临时返回降级数据(如默认值或缓存空值)。
替代Redis的缓存方案
Memcached:纯内存缓存,支持简单键值存储,性能高但缺乏持久化和复杂数据结构。
Ehcache:Java本地缓存,支持堆内和堆外存储,适合单机场景。
Tair:阿里开源的分布式缓存,支持持久化和多种数据结构。
防止数据库被打爆的其他方法
限流:通过令牌桶、漏桶算法限制请求速率(如Guava RateLimiter)。
队列削峰:使用消息队列(如Kafka、RocketMQ)异步处理请求,平滑流量峰值。
读写分离:将读请求分流到从库,减轻主库压力。
高并发与高可用的区别
高并发:系统在单位时间内处理大量请求的能力(如QPS、TPS),侧重性能优化(如缓存、异步、无状态设计)。
高可用:系统在部分组件故障时仍能正常提供服务的能力(如SLA 99.99%),侧重容错设计(如冗余、降级、熔断)。
提升系统高可用的方案
冗余设计:部署多副本(如主从复制、集群),避免单点故障。
故障转移:使用负载均衡(如Nginx)或服务发现(如Eureka)自动切换故障节点。
数据备份:定期备份数据并验证恢复流程(如全量+增量备份)。
暴力解法:遍历所有三元组,计算斜率是否相等(时间复杂度O(n3))。
优化方法:
哈希表存储斜率:固定一个点,计算其他点与该点的斜率,统计相同斜率的数量(需处理垂直斜率情况)。
向量叉积:对于三点A(x1,y1)、B(x2,y2)、C(x3,y3),若向量AB与AC的叉积为0(即(x2-x1)*(y3-y1) - (y2-y1)*(x3-x1) == 0),则三点共线(时间复杂度O(n2))。