【面试】 Java中级开发工程师面试精选:深度问题与实战解析..

🔥 问题1:HashMap与ConcurrentHashMap的线程安全差异

问题
在多线程环境下,HashMapConcurrentHashMap的线程安全性有何本质区别?请结合缓存雪崩场景说明如何选择。

答案

  • 本质区别
    HashMap非线程安全,多线程并发put可能导致链表成环(JDK1.7)或数据覆盖(JDK1.8);
    ConcurrentHashMap通过 分段锁(JDK1.7)或 CAS + synchronized(JDK1.8)保证线程安全,锁粒度细化到桶级别。
  • 缓存雪崩场景
    假设高并发查询商品库存,缓存失效后大量请求穿透到数据库。
    选择ConcurrentHashMap
    private final ConcurrentHashMap<String, AtomicInteger> stockCache = new ConcurrentHashMap<>();  
    
    public void deductStock(String productId) {  
        stockCache.compute(productId, (k, v) -> {  
            if (v == null) v = new AtomicInteger(loadFromDB(k)); // 惰性加载  
            return v.decrementAndGet() >= 0 ? v : null; // 原子减库存  
        });  
    }  
    
    优势compute()方法保证单键操作的原子性,避免超卖;分段锁支持高并发更新。

⚡ 场景题1:分布式锁下的资源竞争

问题
某支付系统需保证同用户10秒内仅允许1次提现操作。若用Redis分布式锁实现,如何解决锁失效时间与业务执行时间的不确定性?

答案
方案锁续期(WatchDog) + 唯一令牌

public boolean tryWithdraw(String userId, double amount) {  
    String lockKey = "lock:withdraw:" + userId;  
    String token = UUID.randomUUID().toString(); // 唯一标识  
    try {  
        // 尝试加锁(SET lockKey token NX PX 10000)  
        if (redis.set(lockKey, token, "NX", "PX", 10000)) {  
            // 启动WatchDog后台线程续期锁(每3秒续期10秒)  
            startWatchDog(lockKey, token);  
            if (checkWithdrawLimit(userId)) { // 业务逻辑  
                executeWithdraw(userId, amount);  
                return true;  
            }  
        }  
        return false;  
    } finally {  
        // 释放锁时验证token(Lua脚本保证原子性)  
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";  
        redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(token));  
    }  
}  

关键点

  1. 唯一令牌:避免误删其他线程的锁;
  2. 锁续期:防止业务未执行完锁过期;
  3. Lua原子解锁:保证判断与删除的原子性。

💡 问题2:JVM内存模型与OOM实战

问题
线上服务频繁触发OutOfMemoryError: Metaspace,如何定位?列举3种优化策略。

答案
定位步骤

  1. jcmd <pid> VM.flags 检查Metaspace配置(-XX:MaxMetaspaceSize=256m);
  2. jstat -gcmetacapacity <pid> 观察元空间使用趋势;
  3. MAT工具分析ClassLoader泄露(如动态生成类未卸载)。

优化策略

  1. 限制动态代理类:减少CGLib/ASM生成的类;
  2. 配置回收阈值-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
  3. 重启策略:通过容器化定时重启实例释放元空间。

🔧 场景题2:线程池拒绝策略设计

问题
订单系统需异步处理10万+/日的物流推送,线程池配置为core=5, max=20, queue=100
突发流量下队列满且线程数达max,如何自定义拒绝策略保证关键订单不丢失

答案
方案降级写入MQ + 告警

ThreadPoolExecutor executor = new ThreadPoolExecutor(  
  5, 20, 30, TimeUnit.SECONDS,  
  new ArrayBlockingQueue<>(100),  
  new CustomRejectedExecutionHandler() // 自定义拒绝策略  
);  

// 自定义拒绝策略  
static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {  
    @Override  
    public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {  
        if (task instanceof LogisticsTask) {  
            LogisticsTask logisticsTask = (LogisticsTask) task;  
            if (logisticsTask.isHighPriority()) {  
                // 关键订单写入RabbitMQ/Kafka  
                mqClient.send("ORDER_FALLBACK_QUEUE", logisticsTask);  
            } else {  
                log.warn("丢弃普通订单: {}", logisticsTask.getOrderId());  
            }  
            triggerAlert(); // 触发扩容告警  
        }  
    }  
}  

效果

  • 关键订单通过MQ异步补偿;
  • 普通订单丢弃避免拖垮服务;
  • 告警触发K8s自动扩容Pod。

✅ 总结考察点

  1. 并发编程:线程安全容器、锁机制、原子操作;
  2. JVM调优:内存模型、OOM排查、GC策略;
  3. 分布式设计:分布式锁、降级策略、异步补偿;
  4. 框架实战:线程池参数化设计、资源竞争解决方案。

提示:实际面试中可延伸追问细节(如synchronized锁升级过程、ThreadLocal内存泄露案例),验证候选人知识深度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小冷coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值