面试官:请简要介绍一下 Java 中的多线程机制,以及如何创建一个线程。
王铁牛:多线程就是多个线程同时运行嘛。创建线程可以通过继承 Thread 类或者实现 Runnable 接口。
面试官:不错,回答得很简洁。那线程池有什么作用,如何创建一个线程池?
王铁牛:线程池能提高线程的复用性,减少创建和销毁线程的开销。创建线程池可以用 ThreadPoolExecutor 类。
面试官:很好。再问你,HashMap 在多线程环境下会出现什么问题,如何解决?
王铁牛:呃……会出现数据丢失啥的吧,解决办法嘛……我想想,好像是用 ConcurrentHashMap 来替代。
面试官:第一轮面试结束,整体表现还行。接下来第二轮,说说 JVM 的内存结构。
王铁牛:JVM 内存结构包括堆、栈、方法区等。
面试官:那类加载机制的过程是怎样的?
王铁牛:类加载机制包括加载、验证、准备、解析、初始化。
面试官:JVM 如何进行垃圾回收的?
王铁牛:这个……好像是通过标记清除、标记整理、复制算法这些来回收垃圾。
面试官:第二轮面试结束。现在进入第三轮,讲讲 Spring 的核心特性。
王铁牛:Spring 有依赖注入、面向切面编程这些特性。
面试官:Spring Boot 相对于 Spring 有哪些优势?
王铁牛:Spring Boot 更简单,自动配置啥的,让开发更快。
面试官:说说 MyBatis 的工作原理。
王铁牛:就是通过 XML 配置文件或者注解来映射数据库操作。
面试官:好,三轮面试结束。回家等通知吧。
答案:
- Java 多线程机制及创建线程:
- Java 多线程是指程序中包含多个执行单元,这些执行单元可以同时执行不同的任务。
- 创建线程的方式主要有两种:
- 继承 Thread 类:定义一个类继承 Thread 类,重写 run 方法,在 run 方法中编写线程执行的代码。然后创建该类的实例对象,调用 start 方法启动线程。例如:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行的代码");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
- 实现 Runnable 接口:定义一个类实现 Runnable 接口,实现 run 方法。然后创建 Thread 类的实例对象,并将实现了 Runnable 接口的类的实例作为参数传递给 Thread 类的构造函数,最后调用 start 方法启动线程。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行的代码");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
- 线程池的作用及创建:
- 线程池的作用:
- 提高线程的复用性,减少线程创建和销毁的开销,从而提高系统性能。
- 便于线程管理,例如可以控制线程池中的线程数量,避免过多线程导致系统资源耗尽。
- 创建线程池可以使用 ThreadPoolExecutor 类,示例代码如下:
- 线程池的作用:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为 5 的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("执行任务 " + taskId);
});
}
executorService.shutdown();
}
}
这里通过 Executors.newFixedThreadPool(5) 创建了一个固定大小为 5 的线程池,后续提交的任务会在这 5 个线程中复用执行。 3. HashMap 在多线程环境下的问题及解决:
- HashMap 在多线程环境下可能会出现数据丢失、死循环等问题。
- 原因是在扩容时,可能会导致链表形成环形结构,从而在 get 操作时出现死循环。
- 解决办法是使用 ConcurrentHashMap,它采用了分段锁机制,在保证线程安全的同时提高了并发性能。例如:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
Integer value = map.get("key1");
System.out.println(value);
}
}
- JVM 的内存结构:
- JVM 内存结构主要包括:
- 堆(Heap):是 JVM 中最大的一块内存区域,用于存储对象实例。它被划分为新生代、老年代等不同区域,不同区域有不同的垃圾回收策略。
- 栈(Stack):每个线程都有自己独立的栈,用于存储局部变量、方法调用等信息。
- 方法区(Method Area):用于存储类信息、常量、静态变量等。
- 程序计数器(Program Counter Register):记录当前线程执行的字节码指令地址。
- JVM 内存结构主要包括:
- 类加载机制的过程:
- 类加载机制包括以下几个过程:
- 加载:将类的字节码文件从磁盘加载到内存中。
- 验证:检查加载的字节码文件是否符合 JVM 的规范,确保其安全性。
- 准备:为类的静态变量分配内存,并设置初始值。
- 解析:将字节码文件中的符号引用转换为直接引用。
- 初始化:执行类的静态代码块,为静态变量赋值等操作。
- 类加载机制包括以下几个过程:
- JVM 垃圾回收:
- JVM 进行垃圾回收主要通过以下几种算法:
- 标记清除算法:先标记出所有需要回收的对象,然后统一回收。这种算法会产生内存碎片。
- 标记整理算法:先标记出需要回收的对象,然后将存活对象向一端移动,最后清理边界以外的内存。
- 复制算法:将内存分为两块,每次只使用其中一块,当这一块内存满了,就将存活对象复制到另一块内存,然后清理原来的内存。
- 例如,在新生代中,一般采用复制算法,因为新生代对象存活率低。老年代则可能采用标记清除或标记整理算法。
- JVM 进行垃圾回收主要通过以下几种算法:
- Spring 的核心特性:
- 依赖注入(Dependency Injection):通过容器将依赖对象注入到目标对象中,降低对象之间的耦合度。例如:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
@Component
class UserDao {
}
这里 UserService 通过 @Autowired 注解将 UserDao 注入进来。
- 面向切面编程(Aspect Oriented Programming,AOP):通过预编译方式和运行期动态代理实现程序功能的统一维护。可以将一些横切关注点(如日志、事务管理等)从业务逻辑中分离出来。例如:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("方法执行前记录日志");
}
}
这个切面类会在指定的业务方法执行前记录日志。 8. Spring Boot 相对于 Spring 的优势:
- Spring Boot 更简单,它采用了自动配置的机制,极大地减少了开发过程中的配置工作量。
- 它内置了 Tomcat 等服务器,可直接运行,无需像 Spring 那样进行繁琐的服务器配置。
- 提供了大量的 Starter 依赖,方便快速集成各种功能,例如 Spring Boot Starter Web 可以快速搭建一个 Web 应用。
- MyBatis 的工作原理:
- MyBatis 通过 XML 配置文件或者注解来映射数据库操作。
- 其工作流程大致如下:
- 读取配置文件,解析 SQL 语句和映射关系。
- 根据映射关系创建 SqlSessionFactory。
- 通过 SqlSessionFactory 创建 SqlSession。
- 使用 SqlSession 执行 SQL 操作,如查询、插入、更新、删除等。
- 例如,通过 XML 配置文件定义 SQL 语句:
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
在 Java 代码中使用:
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class UserService {
private SqlSessionFactory sqlSessionFactory;
public UserService(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
public User getUserById(int id) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
return sqlSession.selectOne("com.example.mapper.UserMapper.getUserById", id);
}
}
}
这里通过 MyBatis 的映射配置和 SqlSession 来执行查询数据库获取用户的操作。