当前位置: 首页 > article >正文

每日 Java 面试题分享【第 18 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:什么是 Java 中的双亲委派模型?
  • 问题二:Java 中 wait() 和 sleep() 的区别?
  • 问题三:Java 和 Go 的区别

问题一:什么是 Java 中的双亲委派模型?

1. 直接回答技术点(考察基础理解)

双亲委派模型(Parent Delegation Model) 是 Java 类加载器(ClassLoader)的工作机制,核心规则是:

  • 向上委派:当一个类加载器收到类加载请求时,先不自己加载,而是递归地将请求委派给父类加载器处理。
  • 向下传递:只有父类加载器无法加载(搜索范围内找不到该类)时,子类加载器才会尝试自己加载。

目标保证类的唯一性和安全性,避免重复加载核心类(如 java.lang.Object)或恶意替换核心类。


2. 底层原理深入(考察源码和机制)

核心源码逻辑(以 ClassLoader.loadClass() 为例)
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已加载过该类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 递归调用父类加载器的 loadClass() 方法
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 父类为 Bootstrap ClassLoader(由C++实现,无Java对象)
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {}
            
            // 3. 父类加载器无法加载时,调用自身的 findClass() 方法
            if (c == null) {
                c = findClass(name);
            }
        }
        return c;
    }
}
关键设计
  • 层级关系:类加载器分为 Bootstrap → Extension → Application → 自定义 ClassLoader 层级。
  • 破坏双亲委派:某些场景需要打破该模型(如 Tomcat 为隔离 Web 应用、JDBC 驱动加载),通过重写 loadClass() 逻辑实现。

3. 结合项目实战(考察应用能力)

场景 1:动态加载外部插件
  • 需求:在电商系统中,需要动态加载第三方支付插件(如支付宝、微信支付)。

  • 实现:自定义类加载器,从指定目录加载插件 JAR 包,避免与主应用的类冲突

  • 关键代码

    public class PluginClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) {
            // 从插件目录读取字节码,调用 defineClass() 生成类对象
            byte[] bytes = loadPluginData(name);
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
    
场景 2:解决依赖冲突
  • 问题:Spring Boot 应用中同时依赖 fastjson 1.xfastjson 2.x,导致 NoSuchMethodError
  • 方案:通过自定义类加载器隔离不同版本的库(类似 OSGI 机制),确保每个模块使用独立依赖

4. 对比延伸(考察知识广度)

类加载器类型加载路径典型应用场景
BootstrapJRE/lib/rt.jar 等核心库加载 java.lang.* 等基础类
ExtensionJRE/lib/ext 目录加载扩展类(如早期 JDBC 驱动)
Application类路径(ClassPath)加载用户编写的应用类
自定义 ClassLoader任意路径(网络、文件、内存等)热部署、模块化、字节码增强
打破双亲委派的典型案例
  • Tomcat:每个 Web 应用使用独立的 WebappClassLoader,优先加载自身 /WEB-INF/classes 下的类,避免应用间类污染。
  • JDBC Driver:通过 ServiceLoader 机制加载不同厂商的驱动(依赖 ClassLoader.getSystemClassLoader())。

5. 避坑指南(考察常见错误)

  • 坑 1:自定义类加载器未正确重写 findClass(),而是错误覆盖 loadClass(),导致双亲委派被破坏。
  • 坑 2:多线程环境下同一类被不同类加载器加载,导致 instanceof 判断失效(不同类加载器加载的类不相等)。
  • 案例:某金融系统因使用多个类加载器加载同一工具类,引发序列化异常,最终通过统一类加载路径解决。

回答逻辑总结
  1. 技术定义 → 2. 源码机制 → 3. 项目适配 → 4. 横向对比 → 5. 避坑经验
    通过从基础到源码、从理论到实战的递进,体现对类加载机制的全盘理解,符合大厂对 " 底层原理 + 落地经验 " 的考察要求。

关联问题扩展

  • 如何实现一个热部署功能?(结合自定义类加载器)
  • 什么是 SPI 机制?为什么 JDBC SPI 打破了双亲委派?
  • Class.forName() 和 ClassLoader.loadClass() 的区别?

问题二:Java 中 wait() 和 sleep() 的区别?

1. 直接回答技术点(考察基础理解)

wait()sleep() 是 Java 多线程中用于线程暂停的两种机制,核心区别如下:

区别点wait()sleep()
所属类Object 类的方法Thread 类的静态方法
锁释放释放锁(需在同步块中调用)不释放锁
唤醒机制需其他线程调用 notify()/notifyAll()超时自动恢复,或被 interrupt() 中断
调用条件必须在 synchronized 块中调用可在任意位置调用

2. 底层原理深入(考察源码和机制)

wait() 的底层原理
  • 依赖对象监视器(Monitor):调用 wait() 的线程必须先通过 synchronized 获取对象锁,否则抛出 IllegalMonitorStateException
  • 线程状态变更:线程从 RUNNABLE 进入 WAITING(无超时)或 TIMED_WAITING(带超时)。
  • JVM 实现:通过操作系统的条件变量(Condition Variable)实现等待/通知机制。
sleep() 的底层原理
  • 不依赖锁:直接暂停当前线程,不涉及锁操作。
  • 线程状态变更:线程进入 TIMED_WAITING 状态。
  • JVM 实现:通过调用系统级 sleep() 函数(如 POSIX 的 nanosleep)实现精准暂停。

代码示例

// wait() 使用示例(需在同步块中)
synchronized (lock) {
    lock.wait(); // 释放锁并等待唤醒
}

// sleep() 使用示例(无需锁)
Thread.sleep(1000); // 线程暂停1秒,不释放锁

3. 结合项目实战(考察应用能力)

场景 1:生产者 - 消费者模型
  • 需求:生产者生产数据后通知消费者消费,缓冲区空时消费者等待。

  • 实现:使用 wait()/notify() 实现线程协作:

    public class Buffer {
        private Queue<Integer> queue = new LinkedList<>();
        
        public synchronized void produce(int value) {
            while (queue.size() >= 10) {
                wait(); // 缓冲区满,生产者等待
            }
            queue.add(value);
            notifyAll(); // 唤醒消费者
        }
        
        public synchronized int consume() {
            while (queue.isEmpty()) {
                wait(); // 缓冲区空,消费者等待
            }
            int value = queue.poll();
            notifyAll(); // 唤醒生产者
            return value;
        }
    }
    
场景 2:定时任务轮询
  • 需求:每隔 5 秒检查一次系统状态。

  • 实现:使用 sleep() 简单实现(注意:生产环境建议用 ScheduledExecutorService):

    public void checkSystemStatus() {
        while (true) {
            // 检查状态逻辑…
            try {
                Thread.sleep(5000); // 不涉及锁,直接暂停
            } catch (InterruptedException e) {
                // 处理中断
            }
        }
    }
    

4. 对比延伸(考察知识广度)

对比维度wait()sleep()Condition.await()(JUC)
锁释放是(需绑定 Lock)
精准唤醒依赖 notify() 的随机性支持 signal() 指定唤醒线程
适用场景线程间协作简单暂停复杂条件等待(如多个等待队列)
为什么生产环境推荐使用 JUC 工具?
  • wait()/notify() 易导致死锁或信号丢失(如过早调用 notify())。
  • Condition 提供更灵活的等待/通知机制,结合 Lock 可支持多个条件队列。

5. 避坑指南(考察常见错误)

  • 坑 1:未在同步块中调用 wait(),导致 IllegalMonitorStateException
  • 坑 2:混淆 sleep()wait() 的锁释放行为,误用 sleep() 导致死锁。
  • 案例:某订单系统在高并发下因错误使用 sleep() 未释放锁,导致线程堆积,最终服务崩溃。改用 wait() 后性能恢复。
最佳实践
  • 优先使用 JUC 工具:如 BlockingQueueCountDownLatch 替代 wait()/notify()
  • 明确线程状态管理:通过线程池控制任务调度,避免手动调用 sleep()

回答逻辑总结
  1. 技术定义 → 2. 源码机制 → 3. 项目适配 → 4. 横向对比 → 5. 避坑经验
    从基础到源码,结合真实场景和对比分析,体现对多线程协作机制的深度理解,符合大厂对 " 原理扎实 + 实战经验 " 的考核要求。

关联问题扩展

  • 如何正确中断一个正在 sleep()wait() 的线程?
  • notify()notifyAll() 的使用场景有什么区别?
  • 为什么 Conditionwait()/notify() 更灵活?

如需进一步探讨,欢迎随时提问!


问题三:Java 和 Go 的区别

1. 直接回答技术点(考察基础理解)

Java 和 Go 是两种不同范式的编程语言,核心区别如下:

维度JavaGo
范式面向对象(OOP)面向过程 + 接口的组合式抽象
运行时基于 JVM(解释执行 + JIT 编译)直接编译为机器码(无虚拟机)
并发模型基于线程/锁(ThreadExecutor基于轻量级协程(Goroutine)和 Channel
内存管理垃圾回收(GC)垃圾回收(GC)但更轻量
语法复杂度语法冗长(显式类型、异常处理)语法极简(隐式接口、错误码返回)

2. 底层原理深入(考察源码和机制)

并发模型对比
  • Java 线程模型

    • 基于操作系统线程(1:1 模型),线程创建和切换成本高(涉及内核态切换)。
    • 依赖 synchronizedReentrantLock 等锁机制,易引发死锁和竞争问题。
    // Java 线程示例
    new Thread(() -> {
        System.out.println("Java thread running");
    }).start();
    
  • Go 协程模型

    • Goroutine 是用户态线程(M:N 调度模型),由 Go 运行时调度,创建成本极低(初始栈 2KB)。
    • 通过 Channel 实现 CSP(Communicating Sequential Processes)模型,避免显式锁。
    // Go 协程示例
    go func() {
        fmt.Println("Goroutine running")
    }()
    
内存管理对比
  • Java GC
    • 分代收集(Young/Old Generation),STW(Stop-The-World)停顿较长,需调优参数(如 G1、ZGC)。
    • 典型问题:内存泄漏(如静态集合持有对象)、Full GC 导致服务卡顿。
  • Go GC
    • 三色标记法,STW 时间更短(通常 <1ms),无需复杂调优。
    • 逃逸分析自动决定对象分配在栈还是堆,减少 GC 压力。

3. 结合项目实战(考察应用能力)

场景 1:高并发 API 网关
  • 需求:每秒处理 10 万 + 请求,要求低延迟、高吞吐。
  • 选择 Go
    • Goroutine 轻量级,可轻松创建数万并发协程。
    • 标准库提供高性能 HTTP 服务器(如 net/http),天然适合 IO 密集型场景。
  • Java 局限
    • 传统线程模型难以支撑超高并发,需依赖异步框架(如 Netty),开发复杂度高。
场景 2:复杂业务中台系统
  • 需求:金融交易系统,需强事务、复杂业务逻辑分层。
  • 选择 Java
    • Spring 生态完善(Spring Boot、Spring Cloud),ORM(如 MyBatis)、声明式事务支持成熟。
    • 面向对象更适合大型系统的领域建模。
  • Go 局限
    • 缺乏成熟的 ORM 框架,事务管理需手动实现。
    • 接口隐式实现导致代码结构松散,大型项目维护成本高。

4. 对比延伸(考察知识广度)

扩展维度JavaGo
生态工具链Maven/Gradle、IDEA 强大支持Go Modules、VS Code 插件生态
部署效率需打包 JAR/WAR + 依赖 JVM 环境单文件二进制部署(无外部依赖)
微服务支持Spring Cloud(注册中心、配置中心)原生 HTTP/RPC + 轻量框架(如 Go Kit)
云原生适配较重(需配合 Quarkus 等优化)天然适配(Kubernetes 用 Go 编写)
性能对比(典型场景)
  • 计算密集型:Java JIT 优化后略优于 Go(如数值计算)。
  • IO 密集型:Go 协程模型显著优于 Java 线程模型(如高频 HTTP 请求处理)。
  • 启动速度:Go 秒级启动 vs Java 依赖 JVM 类加载(秒级到分钟级)。

5. 避坑指南(考察常见错误)

  • Java 误区
    • 滥用线程池导致 OOM(如 newCachedThreadPool 无界队列)。
    • 未合理配置 GC 参数引发频繁 Full GC(如 Young 区过小)。
  • Go 误区
    • Channel 未关闭导致 Goroutine 泄漏(需结合 sync.WaitGroup)。
    • 共享指针引发并发写冲突(尽管有 GC,仍需用 Mutex 保护共享状态)。
典型案例
  • Java 事故:某电商大促期间,线程池队列堆积导致内存溢出,改用异步回调 + 背压机制解决。
  • Go 事故:某社交 App 因未控制 Goroutine 数量,瞬间创建百万协程导致进程崩溃,需添加协程池限流。

回答逻辑总结
  1. 技术定义 → 2. 原理对比 → 3. 场景适配 → 4. 生态扩展 → 5. 避坑实践
    通过多维度对比和真实案例,展示对两种语言特性的深度理解,符合大厂对 " 技术选型能力 " 的考察要求。

关联问题扩展

  • 如何用 Go 实现类似 Java Spring 的依赖注入?
  • Go 的 Channel 底层是如何实现的?
  • Java 虚拟线程(Loom 项目)与 Go 协程有何异同?

如需深入某个技术点,欢迎继续提问!


总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!🎉


http://www.kler.cn/a/529082.html

相关文章:

  • Mybatis-plus缓存
  • pytorch实现循环神经网络
  • docker直接运行arm下的docker
  • HTML(快速入门)
  • 内外网文件摆渡企业常见应用场景和对应方案
  • 单机伪分布Hadoop详细配置
  • Java - 引用类型:强引用、软引用、弱引用和虚引用详解
  • java CountDownLatch和CyclicBarrier
  • Spring AOP 入门教程:基础概念与实现
  • ASP.NET Core 启动并提供静态文件
  • 动态规划两个数组dp问题系列一>不相交的线
  • 一文讲解Java中的HashMap
  • 快速提升网站收录:如何设置网站标签?
  • pandas中的apply方法使用
  • 【漫话机器学习系列】074.异方差(Heteroscedasticity)
  • 【Linux】23.进程间通信(2)
  • 局域网文件互传:手机与电脑的便捷传输利器
  • 《Ollama与DeepSeek》
  • 力扣-链表-142 环形链表Ⅱ
  • AI(计算机视觉)自学路线
  • 【模拟汽笛ISIS】2022-9-15
  • BUUCTF [Black Watch 入群题]PWN1 题解
  • JAVA学习-练习试用Java实现“使用Swing创建一个带有按钮的窗口”
  • 一些计算机零碎知识随写(25年2月)
  • 论文和代码解读:RF-Inversion 图像/视频编辑技术
  • 7 与mint库对象互转宏(macros.rs)