Java通用面试题
·
1. HashMap 线程安全/不安全 & List 线程安全
核心考察点:集合框架的并发特性、线程安全实现方案。
HashMap 线程安全
- 不安全场景:JDK 1.7及之前,多线程
put可能导致环形链表(扩容时头插法导致死循环);JDK 1.8虽修复死循环,但仍可能因并发修改导致数据覆盖(如两个线程同时判断桶为空并插入)。 - 线程安全替代:
ConcurrentHashMap(推荐):JDK 1.8用 CAS + synchronized 实现,锁粒度细(仅锁当前桶),支持高并发;JDK 1.7用分段锁(Segment数组,默认16段,每段独立加锁)。Hashtable:全表锁(synchronized修饰方法),效率低,已被淘汰。
List 线程安全
- 不安全:
ArrayList、LinkedList均未实现同步,多线程增删改可能抛ConcurrentModificationException(快速失败机制)。 - 安全替代:
Vector:全表锁(synchronized修饰方法),效率低。Collections.synchronizedList(new ArrayList<>()):包装类,通过synchronized修饰所有方法,本质是“伪线程安全”(复合操作仍需额外同步,如if(list.isEmpty()) list.add())。CopyOnWriteArrayList(推荐):写时复制(读无锁,写时复制新数组),适合读多写少场景(如配置缓存),缺点是内存占用高、数据一致性弱(最终一致)。
2. HTTPS TLS 加密流程
核心考察点:网络安全基础、TLS握手机制。
TLS 握手核心步骤(以TLS 1.2为例):
- Client Hello:客户端发送支持的TLS版本、加密套件(如
ECDHE-RSA-AES256-GCM-SHA384)、随机数ClientRandom。 - Server Hello:服务端选择TLS版本和加密套件,返回随机数
ServerRandom,并发送证书(含公钥)。 - 证书验证:客户端验证证书合法性(通过CA链校验签名、有效期、域名匹配)。
- 密钥交换:
- 若用 RSA密钥交换:客户端用服务端公钥加密
PreMasterSecret(随机数)发送给服务端,双方通过ClientRandom + ServerRandom + PreMasterSecret生成会话密钥。 - 若用 ECDHE(前向安全):双方通过椭圆曲线算法交换临时公钥,生成
PreMasterSecret(即使私钥泄露,历史通信仍安全)。
- 若用 RSA密钥交换:客户端用服务端公钥加密
- 加密通信:双方用会话密钥(对称加密,如AES)加密后续数据,通过
Finished消息验证握手完整性。
3. Java 常用加密算法
核心考察点:加密算法分类、实际项目应用。
常见算法:
| 类型 | 算法 | 特点与应用场景 |
|---|---|---|
| 对称加密 | AES(推荐)、DES(淘汰)、3DES | 速度快,适合大数据加密;如接口参数加密、数据库敏感字段存储(需管理密钥)。 |
| 非对称加密 | RSA(2048位+)、ECC(椭圆曲线) | 公钥加密私钥解密,适合密钥交换、签名;如JWT令牌签名、HTTPS证书验证。 |
| 哈希算法 | SHA-256、MD5(不安全)、BCrypt | 不可逆,用于密码存储(BCrypt自带盐值,防彩虹表攻击)、数据完整性校验。 |
实战示例:
- 密码存储:用
BCrypt.hashpw(password, BCrypt.gensalt())加密,避免明文或MD5(易被破解)。 - 接口签名:用
HmacSHA256对请求参数+时间戳+密钥生成签名,防篡改。
4. OpenFeign 远程调用实现原理
核心考察点:声明式HTTP客户端、动态代理、负载均衡。
核心流程:
- 接口定义:通过
@FeignClient(name = "service-name")注解标记接口,方法用@GetMapping等定义HTTP请求。 - 动态代理:启动时,OpenFeign扫描接口,通过 JDK动态代理 生成代理类(
FeignInvocationHandler)。 - 请求模板:代理类将方法参数、注解解析为
RequestTemplate(包含URL、Header、Body)。 - 负载均衡:集成 Ribbon(或Spring Cloud LoadBalancer),从注册中心(如Nacos/Eureka)获取服务实例列表,选择一个实例(如轮询、随机)。
- HTTP调用:通过底层客户端(默认
URLConnection,可切换为OkHttp/HttpClient)发送请求,解码响应(如JSON转对象)。
5. 接口加载慢(多表查询)的优化方案
核心考察点:全栈性能优化思维(前端+后端+数据库)。
前端优化:
- 分页/懒加载:避免一次性加载全量数据(如表格分页、滚动触底加载)。
- 缓存:用
localStorage缓存静态数据(如字典表),设置过期时间。 - 骨架屏/占位符:减少用户等待焦虑(如Ant Design的
Skeleton组件)。
后端优化:
- SQL优化:
- 避免
SELECT *,只查必要字段; - 给关联字段(
JOIN条件)、查询条件(WHERE)建索引(用EXPLAIN分析执行计划); - 拆分大查询:将多表
JOIN拆分为多次单表查询(减少锁竞争,适合MySQL)。
- 避免
- 缓存热点数据:用 Redis 缓存查询结果(如用户信息、配置项),设置合理过期时间(防缓存雪崩)。
- 异步处理:非核心逻辑(如日志记录、通知)用
@Async异步执行,缩短主流程耗时。 - 数据库连接池:调优 HikariCP 参数(如
maximum-pool-size匹配CPU核心数)。
数据库优化:
- 读写分离:主库写,从库读(用MyCat/Sharding-JDBC实现),减轻主库压力。
- 分库分表:若单表数据超千万,按业务维度(如用户ID哈希)分表。
6. StringBuffer vs StringBuilder
核心考察点:字符串可变类、线程安全与性能。
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 线程安全 | 线程安全(synchronized修饰方法) |
非线程安全 |
| 性能 | 低(锁竞争开销) | 高(无锁) |
| 适用场景 | 多线程环境(如全局日志拼接) | 单线程环境(如方法内字符串拼接) |
注意:String 是不可变类(每次拼接生成新对象),频繁拼接时用 StringBuilder(单线程)或 StringBuffer(多线程),避免产生大量临时对象。
7. try-return-finally 执行顺序
核心考察点:异常处理流程、finally块特性。
结论:
- try 中无异常:先执行
try代码块,再执行finally代码块,最后执行try中的return。 - try 中有异常:跳过
try剩余代码,执行catch块,再执行finally,最后执行catch中的return(若有)。 - finally 中有 return:覆盖
try/catch中的return(不建议这样写,会破坏异常传播)。
示例:
public static int test() {
try {
return 1;
} finally {
return 2; // 最终返回2,try中的return 1被覆盖
}
}
回答技巧
- 结合项目:每个问题尽量关联自己的实战经验(如“我在优化XX接口时,用Redis缓存了多表查询结果,响应时间从2s降到200ms”)。
- 突出重点:面试官关注“你如何解决实际问题”,而非死记概念(如讲TLS时,强调“前向安全”的重要性,而非背步骤)。
- 承认边界:不确定的点可以说“实际项目中我主要用ConcurrentHashMap,因为XX场景…”,避免硬编。
按这个逻辑回答,既能体现技术深度,又能展示落地能力,面试通过率会大幅提升! 😊
更多推荐

所有评论(0)