多线程是Java开发的核心技能,面试中常考察基础概念与实际应用能力。以下从volatile特性、线程同步、伪共享等维度展开解析。
在Java中,volatile可以修饰数组,但需注意其作用范围——它仅数组引用的可见性,而非数组元素的原子性。例如,当多个线程同时修改同一数组的不同元素时,volatile无法确保操作的原子性;但若修改的是数组引用本身(如指向新数组),则volatile能所有线程及时看到最新引用。这种特性在分布式缓存更新场景中尤为关键,需结合具体业务场景设计同步策略。
面试中常问:"调用wait()时用if还是循环?" 正确实践是基于循环检测条件。因为线程被唤醒后,条件可能因其他线程操作而改变(如生产者-消费者模型中,队列可能被其他线程填满)。标准代码结构如下:
synchronized(obj) { while (!condition) { // 循环检测条件 obj.wait(); } // 执行操作 }
这种设计避免了虚假唤醒(spurious wakeup),确保线程恢复执行时条件依然满足。
JVM机制是高级Java开发的必知内容,面试重点集中在内存模型、GC策略与参数调优。
-XX:+UseCompressedOops是64位JVM的重要优化参数。当JVM从32位迁移至64位时,对象指针从32位扩展为64位,导致内存占用增加约20%。该参数通过压缩对象指针(OOP,Ordinary Object Pointer)至32位,在不影响寻址能力(支持32GB堆内存)的前提下,显著降低内存消耗,提升CPU缓存利用率。
两者均为分代收集器,核心区别在于线程数:Serial是单线程收集,适用于单CPU环境;Parallel(并行收集器)通过多线程执行GC,能更快完成回收,适合多核服务器。需注意,两者都会触发STW(Stop The World),但Parallel通过多线程缩短了停顿时间。
集合类是日常开发的基础工具,面试常考察底层实现与选型逻辑。
ArrayList基于动态数组实现,支持O(1)时间的随机访问,但插入/删除(非尾部)需移动元素,时间复杂度O(n);LinkedList基于双向链表,插入/删除仅需调整指针(O(1)),但随机访问需遍历链表(O(n))。实际开发中,若操作集中在尾部添加或随机访问,优先选ArrayList;若频繁在中间插入/删除,可选LinkedList,但需注意其内存开销(每个节点存储前后指针)。
Java 7中HashMap默认初始容量为16(2^4),且容量必须是2的幂次。这是为了优化哈希冲突,通过位运算(hash & (capacity-1))替代取模运算,提升计算效率。若已知数据量较大(如1000个元素),建议初始化时设置容量为下一个2的幂次(如1024),避免频繁扩容带来的性能损耗。
IO操作是性能瓶颈的常见来源,NIO的非阻塞特性是高并发系统的关键。
直接缓冲区(DirectByteBuffer)通过Unsafe类直接分配堆外内存,避免了Java堆与本地内存的复制操作,适合高频IO场景(如网络传输);非直接缓冲区(HeapByteBuffer)存储在JVM堆中,由GC管理,适合小数据量或对内存敏感的场景。需注意,直接缓冲区的创建与销毁成本较高,需合理复用。
该选项用于禁用Nagle算法。Nagle算法通过合并小数据包减少网络开销,但会引入延迟(等待数据包累积)。在实时性要求高的场景(如游戏、视频通话),启用TCP NO DELAY可立即发送数据,降低延迟;在传输大文件等场景,可禁用该选项以提升吞吐量。
设计模式体现代码设计能力,面试常考察模式应用场景与设计原则。
LSP要求子类能替代父类且不破坏程序正确性。例如,若父类有一个“计算面积”的方法,子类(如正方形)不能修改该方法的语义(如要求边长相等),否则在调用父类方法时可能引发错误。违反LSP会导致系统可维护性下降,需通过组合或接口隔离避免。
策略模式通过接口定义算法族,运行时动态切换实现(如排序策略);模板方法模式在抽象类中定义算法骨架,子类实现具体步骤(如HttpServlet的doGet/doPost)。前者侧重算法的灵活替换,后者侧重流程的统一控制,实际开发中可结合使用(如Spring的ResourceLoader)。
Java面试需兼顾基础与深度:基础题(如多线程、集合)考察知识广度,需熟练掌握核心概念;进阶题(如JVM调优、设计模式)考察技术深度,需结合实际项目经验说明应用场景。建议通过“理解原理→模拟场景→总结应答”三步法系统准备,同时关注最新JDK特性(如Java 8的Lambda、Stream API)以提升竞争力。