互联网大厂面试:Java核心知识、JUC、JVM等技术大考验
王铁牛坐在互联网大厂的面试间里,内心紧张又期待。严肃的面试官坐在他对面,开始了这场决定命运的面试。
第一轮面试 面试官:“首先问几个基础的Java核心知识问题。Java中基本数据类型有哪些?” 王铁牛:“有byte、short、int、long、float、double、char、boolean这八种。” 面试官:“回答正确,不错。那String类为什么是不可变的?” 王铁牛:“因为String类是用final修饰的,它的底层是一个用final修饰的字符数组,一旦创建就不能再改变。” 面试官:“非常好。那在Java中,什么是面向对象的多态性?” 王铁牛:“多态性就是一个对象可以有多种形态。主要通过继承和接口实现,比如父类引用指向子类对象,同一个方法可以根据对象的不同类型有不同的实现。” 面试官:“回答得很清晰,看来基础掌握得不错。”
第二轮面试 面试官:“接下来谈谈JUC相关的内容。说说什么是线程安全?” 王铁牛:“线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致或者其他异常情况。” 面试官:“可以。那CountDownLatch和CyclicBarrier有什么区别?” 王铁牛:“嗯……这个嘛,好像都是和线程同步有关的,具体区别我有点不太确定。” 面试官:“没关系,再问一个。线程池有哪些创建方式?” 王铁牛:“可以通过Executors工厂类创建,像newFixedThreadPool、newCachedThreadPool等,也可以通过ThreadPoolExecutor手动创建。” 面试官:“对了一部分,不过对于刚才没答上来的,下去要好好巩固。”
第三轮面试 面试官:“现在说说Spring相关的。Spring的IOC和AOP是什么?” 王铁牛:“IOC是控制反转,就是把对象的创建和依赖关系的管理交给Spring容器,而不是在代码里手动创建。AOP是面向切面编程,它可以在不修改原有代码的基础上,对程序进行增强,比如实现日志记录、事务管理等功能。” 面试官:“回答得很好。那Spring Boot有什么优点?” 王铁牛:“它可以快速搭建项目,简化配置,有内置的服务器,还能自动配置很多东西。” 面试官:“不错。再问一个MyBatis的问题,MyBatis的一级缓存和二级缓存有什么区别?” 王铁牛:“这个……我记得缓存是为了提高查询效率,但是具体的区别我有点说不清楚了。”
面试官总结:“王铁牛,通过这次面试,我能看到你有一定的Java基础,对于一些常见的基础问题回答得比较准确,像Java基本数据类型、面向对象的多态性,还有Spring的IOC和AOP这些概念都理解得很到位,这是值得肯定的。在JUC和线程池方面,你也能说出一些关键的点,比如线程安全和线程池的创建方式,说明你对这部分知识有一定的了解。
但是,你也存在一些明显的不足。在面对一些稍微复杂或者细节性的问题时,就暴露出知识掌握不扎实的情况。比如CountDownLatch和CyclicBarrier的区别,MyBatis一级缓存和二级缓存的区别,你都没能准确回答出来。这反映出你在知识的深度和广度上还有所欠缺,对于一些关键的技术点没有进行深入的学习和研究。
我们公司对于技术人员的要求是比较高的,不仅要有扎实的基础知识,还需要对相关技术有深入的理解和掌握。目前来看,你距离我们的要求还有一定的差距。不过,学习是一个不断积累和提升的过程,如果你真的想进入这个行业,进入我们公司,建议你回去之后对自己知识的薄弱环节进行针对性的学习和巩固。
我这边的面试就先到这里了,你回家等通知吧。在等待的过程中,也可以继续提升自己的技术能力。祝你好运。”
答案详解
-
Java中基本数据类型有哪些? Java中的基本数据类型分为四类八种:
- 整数类型:byte(1字节,范围 -128 到 127)、short(2字节,范围 -32768 到 32767)、int(4字节,范围 -2147483648 到 2147483647)、long(8字节,范围 -9223372036854775808 到 9223372036854775807)。
- 浮点类型:float(4字节,单精度浮点数)、double(8字节,双精度浮点数)。
- 字符类型:char(2字节,用于表示单个字符)。
- 布尔类型:boolean(只有两个值,true 和 false)。
-
String类为什么是不可变的? String类被设计为不可变的主要有以下几个原因:
- 安全性:在多线程环境下,如果String是可变的,一个线程修改了String的值,可能会影响其他线程的使用,不可变的特性保证了线程安全。
- 缓存:String类内部使用字符数组存储字符串,并且这个数组被final修饰,一旦创建就不能再改变。同时,String类的很多方法(如substring、concat等)返回的都是新的String对象,而不是修改原对象。
- 哈希码缓存:String类重写了hashCode方法,由于String不可变,它的哈希码可以被缓存,提高了哈希表(如HashMap)的性能。
-
在Java中,什么是面向对象的多态性? 多态性是面向对象编程的三大特性之一,它允许不同的对象对同一消息做出不同的响应。主要通过以下两种方式实现:
- 继承:子类继承父类,并重写父类的方法。当通过父类引用指向子类对象时,调用相同的方法会根据实际对象的类型执行不同的实现。
- 接口:一个类可以实现多个接口,不同的类实现同一个接口的方法可以有不同的实现逻辑。
示例代码:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // 输出 "Dog barks"
}
}
-
什么是线程安全? 在多线程环境下,当多个线程同时访问共享资源时,如果不采取任何同步措施,可能会导致数据不一致、数据丢失等问题。线程安全就是指在多线程环境下,对共享资源的访问不会出现这些异常情况。实现线程安全的方式有很多,比如使用synchronized关键字、Lock接口、原子类等。
-
CountDownLatch和CyclicBarrier有什么区别?
- 功能用途:
- CountDownLatch:主要用于一个或多个线程等待其他线程完成操作后再继续执行。它有一个计数器,初始值为一个正整数,每当一个线程完成任务后,计数器减1,当计数器为0时,等待的线程可以继续执行。
- CyclicBarrier:用于多个线程相互等待,当所有线程都到达某个屏障点时,这些线程可以继续执行。它也有一个计数器,当所有线程都调用await方法后,计数器为0,此时所有线程可以继续执行,并且这个屏障可以重复使用。
- 使用场景:
- CountDownLatch:适用于一个主线程等待多个子线程完成任务的场景,比如主线程等待多个子线程完成数据加载。
- CyclicBarrier:适用于多个线程需要同步执行的场景,比如多个运动员同时起跑。
- 功能用途:
示例代码:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
// CountDownLatch 示例
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Thread finished");
latch.countDown();
}).start();
}
latch.await();
System.out.println("All threads finished");
}
}
// CyclicBarrier 示例
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads reached the barrier"));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println("Thread waiting at the barrier");
barrier.await();
System.out.println("Thread continues after the barrier");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
- 线程池有哪些创建方式?
线程池的创建方式主要有两种:
- 通过Executors工厂类创建:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程,则执行任务,否则任务会被放入队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,当有新任务提交时,如果线程池中有空闲线程,则执行任务,否则会创建新的线程。如果线程在一段时间内没有任务执行,会被回收。
- newSingleThreadExecutor:创建一个单线程的线程池,只有一个线程在执行任务,任务会按照提交的顺序依次执行。
- newScheduledThreadPool:创建一个可定时执行任务的线程池,可以执行定时任务和周期性任务。
- 通过ThreadPoolExecutor手动创建:ThreadPoolExecutor是线程池的核心类,通过它可以自定义线程池的各种参数,如核心线程数、最大线程数、线程空闲时间、任务队列等。
- 通过Executors工厂类创建:
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
// 通过 Executors 工厂类创建线程池
public class ExecutorsExample {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
}
fixedThreadPool.shutdown();
}
}
// 通过 ThreadPoolExecutor 手动创建线程池
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 线程空闲时间
java.util.concurrent.TimeUnit.SECONDS,
new java.util.concurrent.LinkedBlockingQueue<>(10) // 任务队列
);
for (int i = 0; i < 10; i++) {
executor.execute(() -> System.out.println(Thread.currentThread().getName()));
}
executor.shutdown();
}
}
- Spring的IOC和AOP是什么?
- IOC(控制反转):是一种设计思想,将对象的创建和依赖关系的管理从代码中剥离出来,交给Spring容器来完成。在传统的编程中,对象的创建和依赖关系的管理是由程序员手动完成的,而在Spring中,通过配置文件或注解的方式,将对象的创建和依赖关系的管理交给Spring容器,这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程):是一种编程范式,它可以在不修改原有代码的基础上,对程序进行增强。AOP的主要概念包括切面(Aspect)、通知(Advice)、连接点(Join Point)、切入点(Pointcut)等。切面是一个包含通知和切入点的模块,通知是在切入点执行的代码,连接点是程序执行过程中的一个点,切入点是一组连接点的集合。
示例代码:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
// 定义一个接口
interface UserService {
void addUser();
}
// 实现接口
class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("Adding user");
}
}
// 定义切面
@Aspect
class LoggingAspect {
@Pointcut("execution(* com.example.UserService.addUser())")
public void addUserPointcut() {}
@Before("addUserPointcut()")
public void beforeAddUser() {
System.out.println("Before adding user");
}
}
// 配置类
@Configuration
class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser();
context.close();
}
}
-
Spring Boot有什么优点?
- 快速搭建项目:Spring Boot提供了很多starter依赖,通过引入这些依赖可以快速搭建项目,减少了配置的工作量。
- 简化配置:Spring Boot有自动配置的功能,它会根据项目中引入的依赖自动进行配置,大多数情况下不需要手动编写配置文件。
- 内置服务器:Spring Boot内置了Tomcat、Jetty等服务器,不需要额外部署服务器,直接运行项目即可。
- 生产就绪特性:Spring Boot提供了很多生产就绪的特性,如健康检查、指标监控、外部配置等,方便在生产环境中使用。
-
MyBatis的一级缓存和二级缓存有什么区别?
- 作用范围:
- 一级缓存:是SqlSession级别的缓存,同一个SqlSession中执行相同的查询语句,会从缓存中获取结果,而不会再次查询数据库。当SqlSession关闭时,一级缓存会被清空。
- 二级缓存:是Mapper级别的缓存,同一个Mapper中执行相同的查询语句,会从缓存中获取结果。二级缓存是跨SqlSession的,不同的SqlSession可以共享二级缓存。
- 开启方式:
- 一级缓存:默认是开启的,不需要额外配置。
- 二级缓存:需要在Mapper.xml文件中添加
<cache/>标签或者在Mapper接口上添加@CacheNamespace注解来开启。
- 作用范围:
示例代码:
<!-- Mapper.xml 开启二级缓存 -->
<mapper namespace="com.example.UserMapper">
<cache/>
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>