互联网大厂Java求职者面试大揭秘:核心知识全考验
面试官:好了,面试开始。第一轮第一个问题,简述一下Java多线程的实现方式,以及在实际业务场景中如何选择合适的方式?
王铁牛:Java多线程实现方式有继承Thread类、实现Runnable接口、实现Callable接口。在实际业务场景中,如果只是简单的任务执行,实现Runnable接口就可以,因为它避免了单继承的局限性。要是有返回值,那就选择实现Callable接口。
面试官:回答得不错。那第二个问题,说说线程池的核心参数有哪些,以及它们的作用分别是什么?
王铁牛:线程池核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程存活时间,unit是时间单位,workQueue是任务队列,threadFactory是线程工厂,handler是拒绝策略。
面试官:嗯,掌握得还可以。第三个问题,在高并发场景下,如何合理配置线程池参数来提高系统性能?
王铁牛:这个嘛,我觉得首先要根据任务的类型来确定corePoolSize,如果是CPU密集型任务,corePoolSize可以设置为CPU核心数 + 1;如果是IO密集型任务,corePoolSize可以设置得大一些。然后根据系统的负载情况来调整maximumPoolSize,workQueue的大小也要根据任务量来定。
面试官:第一轮面试结束。现在进入第二轮。第一个问题,讲讲HashMap的底层实现原理,以及在多线程环境下可能会出现的问题?
王铁牛:HashMap底层是数组 + 链表 + 红黑树实现的。在多线程环境下,可能会出现数据丢失、死循环等问题。
面试官:第二个问题,如何解决HashMap在多线程环境下的问题?
王铁牛:可以使用ConcurrentHashMap,它采用了分段锁机制,提高了并发性能。
面试官:第三个问题,对比一下HashMap和HashTable的区别?
王铁牛:HashMap是非线程安全的,HashTable是线程安全的。HashMap允许key和value为null,HashTable不允许。
面试官:第二轮面试结束。进入第三轮。第一个问题,简述Spring的核心特性有哪些?
王铁牛:Spring的核心特性有依赖注入、面向切面编程、IoC容器等。
面试官:第二个问题,Spring Boot和Spring的关系是什么,它有哪些优势?
王铁牛:Spring Boot是基于Spring的,它简化了Spring应用的开发,提供了自动配置等功能,让开发更快速。
面试官:第三个问题,说说MyBatis的工作原理?
王铁牛:这个,就是通过SQL映射文件来配置SQL语句,然后通过接口调用,MyBatis会帮我们执行SQL。
面试官:面试结束了,回去等通知吧。
答案:
第一轮问题答案
-
Java多线程实现方式及选择:
- 继承Thread类:通过继承Thread类并重写run方法来创建线程。这种方式的缺点是Java是单继承,一个类继承了Thread类就不能再继承其他类了。例如:
class MyThread extends Thread { @Override public void run() { System.out.println("This is a thread."); } }- 实现Runnable接口:实现Runnable接口的类需要实现run方法。这种方式避免了单继承的局限性,一个类可以同时实现多个接口。例如:
class MyRunnable implements Runnable { @Override public void run() { System.out.println("This is a runnable task."); } } Thread thread = new Thread(new MyRunnable()); thread.start();- 实现Callable接口:实现Callable接口的类需要实现call方法,并且call方法有返回值。例如:
import java.util.concurrent.Callable; class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "This is a callable result."; } }在选择时,如果只是简单的任务执行,实现Runnable接口即可;如果任务有返回值,就选择实现Callable接口。
-
线程池核心参数及作用:
- corePoolSize:核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。
- maximumPoolSize:最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,线程数会增加到maximumPoolSize,如果超过这个数,就会根据拒绝策略处理任务。
- keepAliveTime:线程存活时间。当线程数大于corePoolSize时,多余的线程在空闲时会存活keepAliveTime这么长时间,之后如果没有新任务,就会被销毁。
- unit:时间单位,用于指定keepAliveTime的时间单位,比如TimeUnit.SECONDS表示秒。
- workQueue:任务队列。用于存放提交的任务,当线程数小于corePoolSize时,新提交的任务会放入这个队列。
- threadFactory:线程工厂。用于创建线程,通过它可以定制线程的名称、优先级等属性。
- handler:拒绝策略。当任务数超过maximumPoolSize且任务队列已满时,会调用这个拒绝策略来处理新提交的任务。
-
高并发场景下线程池参数配置:
- CPU密集型任务:corePoolSize可以设置为CPU核心数 + 1。因为CPU密集型任务主要消耗CPU资源,多一个核心线程可以避免线程频繁创建和销毁的开销。例如,对于4核CPU的机器,corePoolSize可以设置为5。
- IO密集型任务:corePoolSize可以设置得大一些。因为IO密集型任务大部分时间在等待IO操作完成,线程空闲时间较多,所以可以多配置一些核心线程。比如根据实际业务场景,可以将corePoolSize设置为CPU核心数的2 - 3倍。同时,根据系统的负载情况调整maximumPoolSize,如果负载高,适当增大maximumPoolSize;workQueue的大小也要根据任务量来定,如果任务量小,workQueue可以设置得小一些,如果任务量频繁且大,workQueue要设置得足够大以缓冲任务。
第二轮问题答案
-
HashMap底层实现原理及多线程问题:
- 底层实现原理:HashMap底层是数组 + 链表 + 红黑树实现的。初始时,HashMap创建一个长度为16的数组。当插入一个键值对时,首先通过key的hashCode计算出一个哈希值,然后通过哈希值对数组长度取模得到数组的下标。如果该下标对应的位置为空,就直接插入新的键值对。如果该下标对应的位置不为空,就会形成链表或红黑树(当链表长度达到8且数组长度大于等于64时,链表会转换为红黑树),新的键值对会插入到链表或红黑树中。例如:
HashMap<String, Integer> map = new HashMap<>(); map.put("key1", 1);计算“key1”的哈希值,通过取模得到数组下标,然后插入到相应位置。
- 多线程问题:在多线程环境下,可能会出现数据丢失、死循环等问题。比如在扩容时,可能会导致链表形成环形结构,从而在获取元素时出现死循环。当多个线程同时进行插入操作,可能会导致数据覆盖,造成数据丢失。
-
解决HashMap多线程问题的方法: 可以使用ConcurrentHashMap。ConcurrentHashMap采用了分段锁机制,它将整个哈希表分成多个段,每个段都有自己的锁。这样在多线程操作时,不同段的数据可以同时被访问,提高了并发性能。例如:
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key1", 1);
- HashMap和HashTable的区别:
- 线程安全性:HashMap是非线程安全的,在多线程环境下可能会出现问题;HashTable是线程安全的,它的方法都使用了synchronized关键字进行同步。
- 键值对允许情况:HashMap允许key和value为null,HashTable不允许key和value为null。如果在HashTable中插入null键或值,会抛出NullPointerException。
第三轮问题答案
-
Spring的核心特性:
- 依赖注入:通过IoC容器将对象之间的依赖关系进行管理和注入。例如,一个Service类依赖于一个Dao类,在Spring中,可以通过配置将Dao类的实例注入到Service类中,而不需要在Service类中手动创建Dao实例。
- 面向切面编程(AOP):可以将一些横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,以更灵活的方式进行处理。比如在方法执行前后添加日志记录,通过AOP可以很方便地实现,而不需要在每个业务方法中都重复编写日志代码。
- IoC容器:负责创建、配置和管理对象之间的依赖关系,是Spring的核心。它通过读取配置文件(如XML或注解配置)来实例化对象并注入依赖。
-
Spring Boot和Spring的关系及优势:
- 关系:Spring Boot是基于Spring的,它是Spring的进一步简化和拓展。
- 优势:
- 自动配置:Spring Boot提供了大量的自动配置功能,根据项目引入的依赖自动配置相关的组件和属性,大大减少了开发人员的配置工作量。例如,引入了Spring Data JPA依赖,Spring Boot会自动配置好数据源、JPA相关的配置等。
- 快速开发:内置了Tomcat等服务器,直接运行Spring Boot应用就可以启动一个Web服务,无需像传统Spring项目那样进行复杂的服务器配置和部署。同时,它提供了各种starter依赖,方便开发人员快速集成各种功能。
- 约定大于配置:遵循一定的约定,减少了开发过程中的配置文件编写,让开发更加高效。
-
MyBatis的工作原理: MyBatis通过SQL映射文件来配置SQL语句。首先,定义一个Mapper接口,接口方法对应具体的SQL操作。然后,在SQL映射文件中编写SQL语句,通过参数映射和结果映射与Java对象进行交互。当调用Mapper接口的方法时,MyBatis会根据配置找到对应的SQL语句,将方法参数传递给SQL语句进行执行,并将执行结果按照结果映射规则封装成Java对象返回。例如:
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.example.model.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
在Java代码中:
import com.example.mapper.UserMapper;
import com.example.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里通过MyBatis的配置文件和Mapper接口,实现了根据用户ID查询用户信息的功能。