26年2月汇众网络科技 Java开发工程师 一面
1. 注解@Retention中SOURCE、CLASS、RUNTIME三种策略的生效阶段差异?
思路
核心区分注解在“源码、字节码、运行时”三个阶段的留存状态,明确各阶段的可访问性和典型用途。
回答示例
@Retention的三个策略对应注解的生命周期留存阶段,核心差异如下:
- SOURCE:仅留存于源码阶段,编译成字节码(.class)后被丢弃。典型用途:语法校验(如@Override、@SuppressWarnings),仅给编译器看,运行时完全不可见。
- CLASS:留存到字节码阶段,但JVM加载类后不会将注解加载到内存,默认策略。典型用途:字节码增强(如Lombok),运行时通过反射无法获取。
- RUNTIME:留存到运行时,注解被加载到JVM内存中。典型用途:反射获取注解信息(如@Controller、@RequestMapping),是开发中最常用的策略。
一句话总结:SOURCE只给编译器用,CLASS给字节码工具用,RUNTIME给运行时反射用。
2. 泛型擦除后,List添加Integer为何编译不报错但运行时可能出错?
思路
先讲泛型擦除的本质(编译期检查、运行时无泛型),再解释编译/运行时的不同校验逻辑。
回答示例
核心原因是泛型仅在编译期生效,运行时会被擦除为原生类型:
- 编译不报错:如果List被定义为非泛型(如List list = new ArrayList()),或通过强制类型转换绕过泛型检查(如(List) new ArrayList ()),编译器无法感知泛型约束,允许添加任意类型(如Integer)。
- 运行时出错:当从List中取值并强制转换为目标类型(如String)时,JVM发现实际是Integer类型,会抛出ClassCastException。
比如:
List list = new ArrayList<String>(); list.add(123); // 编译不报错(泛型已擦除) String s = (String) list.get(0); // 运行时抛ClassCastException
3. LinkedBlockingQueue的takeLock与putLock如何实现生产消费解耦?
思路
核心讲“双锁分离”机制,生产和消费用不同锁,互不阻塞,提升并发效率。
回答示例
LinkedBlockingQueue通过分离的takeLock(消费锁)和putLock(生产锁) 实现生产消费解耦:
- 锁分离:put/offer(生产)操作只加putLock,take/poll(消费)操作只加takeLock,生产和消费互不相干,不会互相阻塞。
- 条件变量配合:putLock关联notFull条件(队列不满时唤醒生产者),takeLock关联notEmpty条件(队列不空时唤醒消费者)。
- 并发提升:相比ArrayBlockingQueue的单锁,双锁允许“生产和消费同时进行”,比如队列有剩余空间时,生产者和消费者可并行操作,大幅提升高并发场景的吞吐量。
4. StampedLock的tryOptimisticRead()失败后应如何处理?
思路
先讲乐观读的本质(无锁),失败后降级为悲观读锁,保证数据一致性。
回答示例
StampedLock的tryOptimisticRead()是无锁乐观读,核心处理逻辑:
- 调用tryOptimisticRead()获取一个“版本戳”,此时不加锁,直接读取数
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【八股真解】精炼最新高频面经 文章被收录于专栏
本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏
查看10道真题和解析
