面试官:欢迎你来面试,第一轮先问你几个基础的 Java 核心知识问题。首先,说说 Java 中的多态是怎么实现的?
王铁牛:多态就是一个对象可以表现出多种形态。比如一个父类引用可以指向它的子类对象,调用子类重写后的方法,这就是多态的体现。
面试官:回答得不错。那再问你,Java 中的接口和抽象类有什么区别?
王铁牛:接口里的方法都是抽象的,不能有具体实现,而且一个类可以实现多个接口;抽象类可以有抽象方法,也可以有具体方法,一个类只能继承一个抽象类。
面试官:很好,看来基础还挺扎实。接下来问你几个 JUC 相关的问题。在 JUC 中,CountDownLatch 是用来做什么的?
王铁牛:这个嘛,它好像是用来让一个线程等待其他线程完成任务后再执行的。
面试官:嗯,大致意思对了。那 CyclicBarrier 又和 CountDownLatch 有什么不同呢?
王铁牛:这个……我不太清楚,感觉有点复杂。
面试官:好,第一轮面试就到这里。
第二轮面试开始,这次问你一些 JVM 相关的问题。首先,讲讲 JVM 的内存模型吧。
王铁牛:JVM 内存模型包括堆、栈、方法区这些,堆里存放对象实例,栈里存局部变量和方法调用信息,方法区存类信息、常量等。
面试官:不错。那类加载机制的过程是怎样的?
王铁牛:类加载机制有加载、验证、准备、解析、初始化这些步骤。
面试官:很好。再问你,什么是垃圾回收算法?
王铁牛:垃圾回收算法有标记清除、标记整理、复制算法这些,哎呀,具体细节我不是特别清楚。
面试官:好,第二轮面试结束。
第三轮面试,问你几个多线程和线程池相关的问题。多线程中,如何解决线程安全问题?
王铁牛:可以用 synchronized 关键字,还有 Lock 接口来加锁。
面试官:那线程池的核心参数有哪些?
王铁牛:好像有 corePoolSize、maximumPoolSize 这些,其他的我就不太记得了。
面试官:嗯。最后一个问题,说说线程池的工作原理。
王铁牛:这个……我真的不太会,感觉很难描述清楚。
面试官:好,面试就到这里,回去等通知吧。
答案:
- Java 多态的实现:一个父类引用可以指向它的子类对象,当调用这个引用的方法时,如果子类重写了该方法,就会执行子类重写后的方法,从而实现多态。这是基于方法动态分派机制,在运行时根据对象的实际类型来决定调用哪个方法。
- Java 中接口和抽象类的区别:
- 接口里的方法都是抽象的,不能有具体实现,并且一个类可以实现多个接口。接口主要用于定义行为规范,让不相关的类可以具有共同的行为。
- 抽象类可以有抽象方法,也可以有具体方法,一个类只能继承一个抽象类。抽象类用于定义一些子类共有的属性和行为,为子类提供一个公共的基础。
- JUC 中 CountDownLatch 的作用:它允许一个或多个线程等待其他一组线程完成操作。通过构造函数传入一个计数值,当调用 countDown 方法时,计数值减 1,当计数值为 0 时,等待的线程会被唤醒继续执行。例如,在一个多线程任务中,主线程需要等待多个子线程都完成各自的任务后再进行后续操作,就可以使用 CountDownLatch。
- JVM 内存模型:
- 堆:存放对象实例,是 JVM 中最大的一块内存区域,被所有线程共享。
- 栈:每个线程都有自己独立的栈空间,用于存储局部变量、方法调用信息等。
- 方法区:存储类信息、常量、静态变量等数据。
- 类加载机制的过程:
- 加载:将类的字节码文件加载到内存中,生成一个 Class 对象。
- 验证:检查加载的字节码文件是否符合 JVM 的规范,确保代码的安全性。
- 准备:为类的静态变量分配内存,并设置默认初始值。
- 解析:将常量池中的符号引用解析为直接引用。
- 初始化:执行类的静态代码块,为静态变量赋予正确的初始值。
- 垃圾回收算法:
- 标记清除算法:先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存空间。这种算法会产生内存碎片。
- 标记整理算法:先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉端边界以外的内存。
- 复制算法:将内存空间分为两块,每次只使用其中一块,当这一块内存使用完后,将存活的对象复制到另一块内存中,然后清理原来的那块内存。适用于对象存活率低的场景。
- 多线程中解决线程安全问题的方法:
- synchronized 关键字:可以修饰方法或代码块,保证同一时刻只有一个线程能访问被修饰的部分。
- Lock 接口:提供了比 synchronized 更灵活的锁控制,如可中断锁、公平锁等。通过 Lock 对象的 lock 和 unlock 方法来实现锁的获取和释放。
- 线程池的核心参数:
- corePoolSize:线程池的核心线程数,当提交的任务数小于 corePoolSize 时,线程池会创建新线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数,当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新线程直到线程数达到 maximumPoolSize。
- keepAliveTime:线程池中非核心线程的存活时间,当线程数大于 corePoolSize 时,非核心线程在空闲一段时间后会被销毁,这个空闲时间就是 keepAliveTime。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,用于存放提交的任务,当线程池的线程都在忙时,新提交的任务会被放入任务队列中。
- 线程池的工作原理:当提交一个任务到线程池时,首先会判断当前线程数是否小于 corePoolSize,如果小于,则创建新线程来执行任务;如果大于等于 corePoolSize,则将任务放入 workQueue 中;如果 workQueue 已满,且当前线程数小于 maximumPoolSize,则创建新线程来执行任务;如果当前线程数已经达到 maximumPoolSize,则根据拒绝策略来处理新提交的任务。线程池中的线程在执行完任务后,如果空闲时间超过 keepAliveTime,非核心线程会被销毁。