《互联网大厂 Java 面试:核心知识、并发与框架深度考察》

32 阅读4分钟

互联网大厂 Java 面试:核心知识、并发与框架深度考察

在互联网大厂的一间安静的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。面试即将开始,一场关于 Java 技术的考验拉开帷幕。

第一轮提问 面试官:首先,我们来聊聊 Java 核心知识。Java 中基本数据类型有哪些? 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那 String 是基本数据类型吗? 王铁牛:不是,String 是引用数据类型,它是一个类。 面试官:很好。那在 Java 中,final 关键字有什么作用? 王铁牛:final 关键字可以修饰类、方法和变量。修饰类时,类不能被继承;修饰方法时,方法不能被重写;修饰变量时,变量变成常量,一旦赋值就不能再改变。 面试官:非常棒,看来你对 Java 核心知识掌握得很扎实。

第二轮提问 面试官:接下来我们谈谈 JUC、多线程和线程池。什么是 JUC? 王铁牛:JUC 就是 java.util.concurrent 包,它提供了一系列用于多线程编程的工具类。 面试官:那创建线程有几种方式? 王铁牛:有四种方式,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池。 面试官:那线程池有什么作用呢? 王铁牛:线程池可以复用线程,减少线程创建和销毁的开销,提高系统性能,还可以控制并发线程的数量,避免资源过度使用。 面试官:回答得很好。那线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列、线程工厂和拒绝策略。 面试官:非常出色,对这块知识很熟悉。

第三轮提问 面试官:现在我们来说说一些框架相关的知识。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:那 Spring Boot 和 Spring 有什么关系呢? 王铁牛:Spring Boot 是基于 Spring 构建的,它简化了 Spring 应用的开发,提供了自动配置等功能,让开发更加便捷。 面试官:MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:这个……嗯……好像一个是占位符,一个是直接替换,具体我有点记不清了。 面试官:那 Dubbo 是什么,有什么作用? 王铁牛:Dubbo 是一个分布式服务框架,能……能实现服务的调用,具体细节我不太清楚了。 面试官:RabbitMq 有什么应用场景? 王铁牛:好像可以用于消息队列,具体怎么用我也说不太明白。

面试官:好的,今天的面试就到这里。你的表现整体还可以,对一些基础的 Java 核心知识、多线程和部分框架的基本概念掌握得不错,但是在一些框架的细节和应用场景方面还需要加强。你先回家等通知吧,我们后续会综合评估后给你答复。

答案详解

  1. Java 基本数据类型
    • Java 有 8 种基本数据类型,可分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但实际实现通常占 1 字节)。基本数据类型直接存储值,而引用数据类型存储的是对象的引用。
  2. String 不是基本数据类型
    • String 是 Java 中的一个类,属于引用数据类型。它用于表示字符串,在 Java 中字符串是不可变的,即一旦创建,其值不能被修改。每次对 String 进行修改操作,实际上是创建了一个新的 String 对象。
  3. final 关键字的作用
    • 修饰类:当一个类被 final 修饰时,该类不能被其他类继承,即它是最终类,不能有子类。例如 final class FinalClass {},任何尝试继承 FinalClass 的类都会编译报错。
    • 修饰方法:被 final 修饰的方法不能被重写。如果父类中有一个 final 方法,子类不能提供该方法的不同实现。例如:
class Parent {
    final void finalMethod() {
        System.out.println("This is a final method.");
    }
}
class Child extends Parent {
    // 以下代码会编译报错
    // void finalMethod() {
    //     System.out.println("Trying to override final method.");
    // }
}
- **修饰变量**:变量一旦被 final 修饰,就变成常量,必须在声明时或构造方法中进行赋值,并且赋值后不能再改变。例如 `final int CONSTANT = 10;`。

4. JUC: - JUC 即 java.util.concurrent 包,是 Java 提供的用于多线程编程的工具包。它包含了很多类和接口,如线程池相关的类(ExecutorService、ThreadPoolExecutor 等)、并发集合(ConcurrentHashMap、CopyOnWriteArrayList 等)、同步工具类(CountDownLatch、CyclicBarrier、Semaphore 等),这些类和接口可以帮助开发者更方便、高效地进行多线程编程。 5. 创建线程的四种方式: - 继承 Thread 类:创建一个类继承自 Thread 类,重写其 run() 方法,然后创建该类的实例并调用 start() 方法启动线程。例如:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running.");
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
- **实现 Runnable 接口**:创建一个类实现 Runnable 接口,实现其 `run()` 方法,然后将该类的实例作为参数传递给 Thread 类的构造方法,再调用 `start()` 方法启动线程。例如:
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable is running.");
    }
}
public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
- **实现 Callable 接口**:创建一个类实现 Callable 接口,实现其 `call()` 方法,该方法有返回值。然后使用 FutureTask 包装 Callable 对象,再将 FutureTask 对象传递给 Thread 类的构造方法,调用 `start()` 方法启动线程。可以通过 FutureTask 的 `get()` 方法获取线程执行的结果。例如:
import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 2;
    }
}
public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer result = futureTask.get();
        System.out.println("Result: " + result);
    }
}
- **使用线程池**:通过 Executors 工厂类创建不同类型的线程池,如 `newFixedThreadPool()``newCachedThreadPool()` 等,然后将实现了 Runnable 或 Callable 接口的任务提交给线程池执行。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Task is running.");
    }
}
public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(new MyTask());
        executorService.shutdown();
    }
}
  1. 线程池的作用
    • 复用线程:线程的创建和销毁需要消耗系统资源,线程池可以复用已经创建的线程,避免频繁创建和销毁线程带来的开销。
    • 提高系统性能:通过复用线程,减少了线程创建和销毁的时间,提高了系统的响应速度和处理效率。
    • 控制并发线程数量:线程池可以设置核心线程数、最大线程数等参数,从而控制并发线程的数量,避免过多线程占用过多系统资源,导致系统性能下降甚至崩溃。
  2. 线程池的核心参数
    • 核心线程数(corePoolSize):线程池中的核心线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量。当任务队列已满,且线程数小于最大线程数时,线程池会创建新的线程来执行任务。
    • 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,空闲线程在经过 keepAliveTime 时间后如果没有新任务,会被销毁。
    • 时间单位(unit):keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
    • 任务队列(workQueue):用于存储等待执行的任务的队列,常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
    • 线程工厂(threadFactory):用于创建线程的工厂,通过线程工厂可以自定义线程的名称、优先级等属性。
    • 拒绝策略(handler):当任务队列已满,且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  3. Spring 的核心特性
    • IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。Spring 容器负责创建对象,并将对象之间的依赖关系注入到对象中。例如,一个类需要依赖另一个类的实例,传统方式是在类内部创建该实例,而在 Spring 中,通过配置或注解将该实例注入到类中。
    • AOP(面向切面编程):AOP 是一种编程范式,它允许开发者在不修改原有代码的情况下,对程序的某些特定部分进行增强。在 Spring 中,AOP 主要用于实现日志记录、事务管理、权限验证等功能。通过定义切面、切入点和通知,将横切关注点(如日志记录)从业务逻辑中分离出来。
  4. Spring Boot 和 Spring 的关系
    • Spring Boot 是基于 Spring 构建的,它简化了 Spring 应用的开发过程。Spring 是一个强大的企业级开发框架,但在配置和开发过程中需要编写大量的配置文件和样板代码。Spring Boot 提供了自动配置功能,通过约定大于配置的原则,减少了开发者的配置工作量。开发者只需要引入相应的依赖,Spring Boot 会自动进行配置,让开发更加便捷、高效。
  5. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行参数设置,这样可以有效防止 SQL 注入攻击。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 在处理 ${} 时,会直接将 ${} 中的内容替换到 SQL 语句中。这种方式存在 SQL 注入风险,通常用于动态表名、动态列名等场景。例如:
<select id="getUserByColumn" parameterType="map" resultType="User">
    SELECT * FROM users WHERE ${column} = #{value}
</select>
  1. Dubbo 是什么及作用
    • Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,它提供了服务注册与发现、远程调用、集群容错、负载均衡等功能。在分布式系统中,不同的服务可能部署在不同的服务器上,Dubbo 可以帮助服务之间进行远程调用,实现服务的解耦和分布式部署。通过服务注册与发现机制,服务提供者可以将自己的服务注册到注册中心,服务消费者可以从注册中心获取服务提供者的地址信息,然后进行远程调用。
  2. RabbitMq 的应用场景
    • 异步处理:将一些耗时的操作(如发送邮件、生成报表等)通过消息队列异步处理,提高系统的响应速度。例如,用户注册时,将发送注册邮件的任务放入 RabbitMq 队列,由专门的消费者线程处理,用户无需等待邮件发送完成即可完成注册流程。
    • 系统解耦:不同的系统或模块之间通过消息队列进行通信,降低系统之间的耦合度。例如,订单系统和库存系统之间通过 RabbitMq 进行消息传递,订单系统创建订单后发送消息到队列,库存系统从队列中获取消息并处理库存扣减操作,这样两个系统可以独立开发和部署。
    • 流量削峰:在高并发场景下,通过消息队列可以将请求进行缓冲,避免瞬间大量请求对系统造成冲击。例如,在电商促销活动中,大量用户同时下单,将订单请求放入 RabbitMq 队列,系统按照一定的速率从队列中取出请求进行处理,保证系统的稳定性。