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为何编译不报错但运行时可能出错?

思路

先讲泛型擦除的本质(编译期检查、运行时无泛型),再解释编译/运行时的不同校验逻辑。

回答示例

核心原因是泛型仅在编译期生效,运行时会被擦除为原生类型

  1. 编译不报错:如果List被定义为非泛型(如List list = new ArrayList()),或通过强制类型转换绕过泛型检查(如(List) new ArrayList ()),编译器无法感知泛型约束,允许添加任意类型(如Integer)。
  2. 运行时出错:当从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(生产锁) 实现生产消费解耦:

  1. 锁分离:put/offer(生产)操作只加putLock,take/poll(消费)操作只加takeLock,生产和消费互不相干,不会互相阻塞。
  2. 条件变量配合:putLock关联notFull条件(队列不满时唤醒生产者),takeLock关联notEmpty条件(队列不空时唤醒消费者)。
  3. 并发提升:相比ArrayBlockingQueue的单锁,双锁允许“生产和消费同时进行”,比如队列有剩余空间时,生产者和消费者可并行操作,大幅提升高并发场景的吞吐量。

4. StampedLock的tryOptimisticRead()失败后应如何处理?

思路

先讲乐观读的本质(无锁),失败后降级为悲观读锁,保证数据一致性。

回答示例

StampedLock的tryOptimisticRead()是无锁乐观读,核心处理逻辑:

  1. 调用tryOptimisticRead()获取一个“版本戳”,此时不加锁,直接读取数

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务