《互联网大厂Java求职者面试:核心知识大考验》

31 阅读3分钟

面试官:请简要介绍一下Java核心知识中面向对象的三大特性。

王铁牛:嗯,面向对象的三大特性嘛,封装、继承、多态。封装就是把数据和操作数据的方法封装在一起,继承就是子类继承父类的属性和方法,多态就是同一个行为具有多个不同表现形式。

面试官:不错,回答得很准确。那说说JUC里的线程池有哪些重要参数,它们的作用是什么?

王铁牛:线程池的重要参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue和threadFactory。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程池线程空闲后的存活时间,unit是存活时间的单位,workQueue是任务队列,threadFactory是线程工厂。

面试官:很好,看来你对这块还是有一定了解的。接下来问个关于JVM的问题,简述一下Java内存区域有哪些?

王铁牛:Java内存区域有堆、栈、方法区、本地方法栈、程序计数器。堆是存放对象实例的地方,栈是存放局部变量和方法调用的地方,方法区存放类信息、常量、静态变量等,本地方法栈是执行本地方法的,程序计数器记录当前线程执行的字节码的行号。

面试官:第一轮面试结束,整体表现还不错,希望你后面两轮也能继续保持。

面试官:说说多线程中如何保证线程安全?

王铁牛:可以用synchronized关键字,还有Lock接口及其实现类,像ReentrantLock。还有使用线程安全的集合类,比如ConcurrentHashMap。

面试官:那讲讲线程池的拒绝策略有哪些?

王铁牛:线程池的拒绝策略有AbortPolicy,直接抛出异常;CallerRunsPolicy,由调用线程处理任务;DiscardPolicy,直接丢弃任务;DiscardOldestPolicy,丢弃队列中最老的任务,然后重新尝试执行任务。

面试官:ArrayList在多线程环境下如何保证线程安全?

王铁牛:嗯……好像是可以用CopyOnWriteArrayList来替代ArrayList,它是线程安全的。

面试官:第二轮面试结束,表现中规中矩,期待你最后一轮的表现。

面试官:说说Spring框架中IoC和AOP的概念。

王铁牛:IoC就是控制反转,把对象的创建和依赖关系的管理交给Spring容器。AOP就是面向切面编程,在不修改原有代码的基础上,动态地将一些功能添加到目标对象上。

面试官:那Spring Boot的自动配置原理是什么?

王铁牛:Spring Boot的自动配置原理是通过条件注解来实现的,根据类路径下的依赖和配置信息,自动配置相应的组件。

面试官:MyBatis的#{}和${}的区别是什么?

王铁牛:#{}是预编译处理,{}是字符串替换。#{}可以防止SQL注入,{}不能防止SQL注入。

面试官:面试结束了,你的表现有好有坏。我们会综合评估后给你通知,回家等消息吧。

答案:

  1. Java核心知识中面向对象的三大特性
    • 封装:封装是把对象的属性和行为结合成一个独立的整体,对外提供统一的访问方式。它隐藏了对象内部的实现细节,提高了代码的安全性和可维护性。例如,一个类中的属性可以用private修饰,通过public的方法来访问和修改这些属性。这样外部代码只能通过规定的方法来操作对象,而不能直接访问和修改对象的内部属性,避免了数据的随意修改和错误访问。
    • 继承:继承是指一个对象直接使用另一对象的属性和方法。在Java中,通过extends关键字实现类的继承。子类继承父类的属性和方法后,可以根据自身需求进行扩展和重写。比如,有一个父类Animal,子类Dog继承自Animal,Dog就可以继承Animal的一些通用属性(如name、age等)和方法(如eat()),同时还可以根据狗的特点重写eat()方法,或者添加自己特有的方法(如bark())。
    • 多态:多态是指同一个行为具有多个不同表现形式。在Java中,多态主要体现在方法的重写和重载上。当子类继承父类并重写了父类的方法时,通过父类引用指向子类对象,调用该方法时会表现出不同的行为。例如,父类有一个方法void display(),子类重写了这个方法,当使用父类引用调用display()方法时,实际执行的是子类重写后的方法。这就是多态的体现,它提高了代码的灵活性和扩展性。
  2. JUC里线程池的重要参数及其作用
    • corePoolSize:核心线程数。线程池创建后,默认情况下会先创建corePoolSize个核心线程来处理任务。当提交的任务数小于corePoolSize时,这些核心线程会一直存在并处理任务。
    • maximumPoolSize:最大线程数。当提交的任务数超过corePoolSize,并且任务队列workQueue已满时,线程池会创建新的线程来处理任务,但线程数最多不会超过maximumPoolSize。
    • keepAliveTime:线程池线程空闲后的存活时间。当线程池中的线程数大于corePoolSize,且这些线程空闲了keepAliveTime这么长的时间后,它们会被销毁,以减少资源消耗。
    • unit:存活时间的单位。与keepAliveTime配合使用,指定存活时间的时间单位,如TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)等。
    • workQueue:任务队列。当提交的任务数大于corePoolSize时,这些任务会被放入workQueue中等待处理。常用的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
    • threadFactory:线程工厂。用于创建线程池中的线程,通过它可以自定义线程的名称、优先级等属性。
  3. Java内存区域
    • :是Java虚拟机所管理的内存中最大的一块。它是被所有线程共享的一块内存区域,在虚拟机启动时创建。堆主要用于存放对象实例,几乎所有的对象实例都在这里分配内存。
    • :与线程紧密相关,每个线程都有自己独立的栈空间。栈中主要存放局部变量和方法调用的上下文信息。当一个方法被调用时,会在栈中为该方法的局部变量分配内存空间,方法执行结束后,这些局部变量所占用的栈空间会被释放。
    • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它也被所有线程共享。
    • 本地方法栈:与虚拟机栈类似,它为虚拟机使用到的Native方法服务。
    • 程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
  4. 多线程中保证线程安全的方法
    • 使用synchronized关键字:synchronized可以修饰方法或代码块。当一个方法或代码块被synchronized修饰时,同一时刻只能有一个线程访问它。例如,修饰方法时,该方法在同一时间只能被一个线程调用;修饰代码块时,只有获取到该对象锁的线程才能执行代码块中的内容。这样就保证了在同一时刻,共享资源不会被多个线程同时修改,从而保证了线程安全。
    • 使用Lock接口及其实现类(如ReentrantLock):Lock接口提供了比synchronized更灵活的锁控制。例如,它可以实现公平锁,即线程按照申请锁的顺序来获取锁,避免了线程饥饿问题;还可以实现可中断锁,线程在等待锁的过程中可以被中断;另外,通过tryLock()方法可以尝试获取锁,如果获取不到可以立即返回,而不是像synchronized那样一直等待。
    • 使用线程安全的集合类:如ConcurrentHashMap。普通的HashMap在多线程环境下是不安全的,因为在扩容等操作时可能会导致数据不一致。而ConcurrentHashMap通过分段锁等机制,保证了在多线程环境下的线程安全,它允许多个线程同时对不同的段进行读写操作,提高了并发性能。
  5. 线程池的拒绝策略
    • AbortPolicy:直接抛出RejectedExecutionException异常。当线程池无法处理新提交的任务时,采用这种策略会立即终止任务提交,并抛出异常,适用于对任务丢失比较敏感的场景。
    • CallerRunsPolicy:由调用线程处理任务。当线程池饱和时,会将任务回退到调用线程,由调用线程来执行该任务。这样可以防止任务丢失,但可能会影响调用线程的性能。
    • DiscardPolicy:直接丢弃任务。当线程池无法处理新任务时,会默默丢弃该任务,不会抛出异常或进行其他处理。适用于对任务丢失不太敏感的场景。
    • DiscardOldestPolicy:丢弃队列中最老的任务,然后重新尝试执行任务。当线程池饱和且任务队列已满时,会丢弃队列中最早进入的任务,然后尝试重新提交新任务,看是否能被处理。
  6. ArrayList在多线程环境下保证线程安全的方法
    • 使用CopyOnWriteArrayList:CopyOnWriteArrayList是Java并发包中的一个线程安全的List实现。它的原理是在写操作时,会先复制一份原数组,然后在新数组上进行写操作,写完后再将新数组赋值给原数组。在读操作时,直接读取原数组的数据。这样在读操作时不需要加锁,从而保证了较高的并发读性能,适用于读多写少的场景。
  7. Spring框架中IoC和AOP的概念
    • IoC(控制反转):传统的应用程序中,对象之间的依赖关系是由对象自己创建和管理的。而在IoC模式下,对象的创建和依赖关系的管理交给了Spring容器。例如,一个类A需要依赖类B来完成某项功能,在IoC容器中,容器会负责创建类B的实例,并将其注入到类A中。这样,类A就不需要自己去创建类B的实例,降低了对象之间的耦合度,提高了代码的可维护性和可测试性。
    • AOP(面向切面编程):AOP是一种编程范式,它允许开发者将一些横切关注点(如日志记录、性能监控、事务管理等)从业务逻辑中分离出来,形成独立的切面。在Spring中,通过AOP可以在不修改原有业务代码的基础上,动态地将这些切面功能织入到目标对象中。例如,通过配置AOP切面,可以在方法执行前后自动记录日志,或者在出现异常时进行统一的异常处理,而不需要在每个业务方法中都重复编写这些代码,提高了代码的复用性和简洁性。
  8. Spring Boot的自动配置原理: Spring Boot的自动配置原理是基于条件注解来实现的。Spring Boot会扫描类路径下的所有jar包和配置类,根据类路径下的依赖和配置信息,自动配置相应的组件。例如,当类路径下存在Spring Data JPA的依赖时,Spring Boot会自动配置JPA相关的组件,包括数据源、EntityManagerFactory、事务管理器等。这是通过一系列的条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)来实现的。@ConditionalOnClass表示当类路径下存在某个特定的类时,才会进行相应的配置;@ConditionalOnMissingBean表示当容器中不存在某个特定的Bean时,才会创建并配置该Bean。通过这些条件注解的组合,Spring Boot能够根据应用的实际依赖情况,自动配置合适的组件,极大地简化了应用的开发和配置过程。
  9. MyBatis的#{}和${}的区别
    • #{}:#{}是预编译处理。在MyBatis中,当使用#{}时,它会将传入的值作为一个参数,通过PreparedStatement进行预编译处理。这样可以防止SQL注入,因为预编译的SQL语句会将参数值作为占位符,而不是直接拼接在SQL语句中。例如,当执行SQL语句“select * from user where id = #{id}”时,MyBatis会将#{id}替换为实际的参数值,并且对参数值进行合法性检查和转义处理,保证了SQL语句的安全性。
    • **{}**:{}是字符串替换。使用时,MyBatis会直接将传入的值替换到SQL语句中,不会进行预编译处理。这样如果传入的值包含恶意的SQL语句片段,就可能导致SQL注入问题。例如,当执行SQL语句“selectfromuserwherename={}时,MyBatis会直接将传入的值替换到SQL语句中,不会进行预编译处理。这样如果传入的值包含恶意的SQL语句片段,就可能导致SQL注入问题。例如,当执行SQL语句“select * from user where name = {name}”时,如果传入的name值为“' or 1 = 1 --”,那么最终执行的SQL语句就会变成“select * from user where name = ' or 1 = 1 --”,可能会导致数据泄露等安全问题。所以在实际应用中,应尽量避免使用${}进行参数绑定,除非确定传入的值是安全的。