面试官:请简要介绍一下 Java 中的多线程机制,以及如何创建和启动一个线程?
王铁牛:多线程就是一个程序中可以同时运行多个线程。创建线程可以通过继承 Thread 类或者实现 Runnable 接口。启动线程就是调用 start 方法。
面试官:不错,回答得很简洁明了。那再问你,什么是线程池?它有什么作用?
王铁牛:线程池就是预先创建一定数量的线程,当有任务到来时,从线程池中获取线程去执行任务。它可以提高线程的复用性,减少线程创建和销毁的开销。
面试官:很好,理解得比较准确。接下来问一些更深入的,在高并发场景下,如何保证线程安全?
王铁牛:可以使用 synchronized 关键字,或者使用 Lock 接口。还有就是要注意共享资源的访问控制。
面试官:第一轮提问结束。接下来进入第二轮。请说一下 HashMap 的底层实现原理。
王铁牛:HashMap 底层是数组 + 链表 + 红黑树。当链表长度超过一定阈值时,会将链表转换为红黑树,以提高查询效率。
面试官:那 HashMap 在扩容时会发生什么?
王铁牛:扩容时会重新计算每个元素的哈希值,然后重新插入到新的数组中。
面试官:如果两个键的哈希值相同,会怎么处理?
王铁牛:哈希值相同的键会形成链表或者红黑树,按照顺序存储。
面试官:第二轮提问结束。现在是第三轮。请阐述一下 Spring 的核心特性。
王铁牛:Spring 有依赖注入、面向切面编程、IoC 容器等特性。
面试官:那 Spring Boot 相对于 Spring 有哪些优势?
王铁牛:Spring Boot 更加简单易用,它可以快速搭建项目,自动配置很多东西。
面试官:最后一个问题,MyBatis 的工作原理是什么?
王铁牛:MyBatis 是通过 XML 或注解配置 SQL 语句,然后通过映射器将 SQL 语句和 Java 对象进行映射。
面试官:面试结束,回去等通知吧。
答案:
- Java 多线程机制及创建启动线程:
- 多线程是指一个程序中可以同时运行多个线程,它们可以并发执行,提高程序的执行效率和响应性。
- 创建线程有两种常见方式:
- 继承 Thread 类:创建一个类继承 Thread 类,重写 run 方法,然后创建该类的实例并调用 start 方法启动线程。例如:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行");
}
}
MyThread thread = new MyThread();
thread.start();
- **实现 Runnable 接口**:创建一个类实现 Runnable 接口,实现 run 方法,然后将该类实例作为参数传递给 Thread 类的构造函数创建线程对象并启动。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
- 线程池:
- 线程池是预先创建一定数量的线程,当有任务到来时,从线程池中获取线程去执行任务,任务执行完后线程不会被销毁,而是放回线程池等待下一个任务。
- 作用:
- 提高线程复用性:避免频繁创建和销毁线程的开销,提高系统性能。
- 便于管理:可以统一管理线程的创建、销毁、调度等。
- 控制资源:可以限制线程池的大小,防止过多线程消耗系统资源。
- 高并发场景下保证线程安全:
- synchronized 关键字:
- 可以修饰方法或者代码块。当修饰方法时,该方法在同一时刻只能被一个线程访问;修饰代码块时,只有获取到锁的线程才能执行代码块中的内容。例如:
- synchronized 关键字:
public synchronized void method() {
// 线程安全的代码
}
synchronized (this) {
// 线程安全的代码
}
- Lock 接口:
- 提供了比 synchronized 更灵活的锁控制。例如 ReentrantLock,可以实现公平锁、可中断锁等。使用时需要手动获取锁和释放锁。例如:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 线程安全的代码
} finally {
lock.unlock();
}
- 共享资源访问控制:
- 对于共享资源,要保证同一时刻只有一个线程能访问修改它。可以使用上面提到的锁机制,也可以使用线程安全的集合类等。
- HashMap 底层实现原理:
- HashMap 底层是数组 + 链表 + 红黑树。
- 它通过计算键的哈希值来确定元素在数组中的位置。当有新元素插入时,先计算哈希值,然后找到对应的数组位置。
- 如果该位置为空,则直接插入新元素。
- 如果该位置不为空,且新元素的哈希值与已有元素的哈希值相同(哈希冲突),则会形成链表或者红黑树(当链表长度超过一定阈值时转换为红黑树)来存储这些元素。
- HashMap 扩容:
- 当 HashMap 中的元素数量达到一定阈值(负载因子 * 容量)时,会进行扩容。
- 扩容时会创建一个新的更大的数组,然后重新计算每个元素的哈希值,将它们重新插入到新的数组中。
- 重新计算哈希值的方式是通过位运算,使得元素尽量均匀地分布在新数组中。
- 哈希值相同的键处理:
- 哈希值相同的键会形成链表或者红黑树。
- 链表中元素按照插入顺序存储,新元素会添加到链表头部。
- 红黑树则是一种自平衡二叉查找树,它可以提高查找效率,元素按照键的大小顺序存储。
- Spring 的核心特性:
- 依赖注入(Dependency Injection):通过控制反转(IoC)容器,将对象之间的依赖关系由程序主动创建改为由容器注入,降低了对象之间的耦合度。例如:
class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
// 在 Spring 配置文件中
<bean id="userDao" class="com.example.UserDaoImpl"/>
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userDao"/>
</bean>
- 面向切面编程(Aspect Oriented Programming, AOP):可以在不修改原有代码的基础上,对业务逻辑进行横向切割,比如日志记录、事务管理等。例如:
@Aspect
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("方法执行前记录日志");
}
}
- IoC 容器:负责创建、配置和管理对象之间的依赖关系,是 Spring 的核心。它通过 XML 配置文件或者注解来定义 bean 及其依赖关系。
- Spring Boot 相对于 Spring 的优势:
- 快速搭建项目:Spring Boot 提供了很多默认配置,能够快速创建一个基于 Spring 的项目框架,减少了大量的配置工作。
- 自动配置:根据项目中引入的依赖,自动配置相关的组件和功能,比如数据库连接、Web 服务器等,极大地简化了开发过程。
- 内置服务器:内置了 Tomcat 等服务器,无需额外配置和部署服务器,方便开发和部署。
- MyBatis 的工作原理:
- MyBatis 通过 XML 或注解配置 SQL 语句。
- 它使用映射器(Mapper)将 SQL 语句和 Java 对象进行映射。
- 当执行 SQL 时,MyBatis 会根据映射关系将 SQL 执行结果封装成 Java 对象返回。例如:
- XML 配置文件中定义 SQL 语句:
<mapper namespace="com.example.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
- Java 代码中调用:
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
- 其中 sqlSession 负责执行 SQL 并管理事务等。MyBatis 通过反射等机制实现对象和 SQL 结果的映射。