Java 面试题解析:线程轮流生产 a-z、生产者消费者模型、Spring Ioc、AOP、事务等
编辑
在 Java 面试中,除了基础的语法和多线程知识外,Spring 框架和事务管理是常见的考察点。本文将详细解答几个常见的 Java 面试问题,涵盖线程控制、生产者消费者模型以及 Spring 中的 IoC、AOP 和事务管理等概念,并通过具体的代码示例进行讲解。编辑
1. 多个线程轮流生产 a-z
这个问题考察了线程之间的协调与控制。通过多线程轮流生产字符 a-z
,可以使用 wait()
和 notify()
方法来控制线程的执行顺序,确保每个线程按顺序执行。编辑
实现思路:
- 使用两个线程交替打印字母,一个线程打印
a, c, e, ...
,另一个线程打印b, d, f, ...
。 - 通过
synchronized
锁住同一个对象,让线程互相等待和通知。编辑
示例:
java
public class PrintLetters {
private static final Object lock = new Object();
private static char currentChar = 'a';
public static void main(String[] args) {
Runnable printEven = () -> {
synchronized (lock) {
for (char c = 'a'; c <= 'z'; c += 2) {
System.out.print(c + " ");
lock.notify();
try {
if (c != 'z') lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
};
Runnable printOdd = () -> {
synchronized (lock) {
for (char c = 'b'; c <= 'z'; c += 2) {
System.out.print(c + " ");
lock.notify();
try {
if (c != 'z') lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
};
Thread thread1 = new Thread(printEven);
Thread thread2 = new Thread(printOdd);
thread1.start();
thread2.start();
}
}
输出:
css
a b c d e f g h i j k l m n o p q r s t u v w x y z
2. 生产者-消费者模型
生产者-消费者问题是经典的多线程问题,要求通过缓冲区实现生产者和消费者的协调。生产者将产品放入缓冲区,消费者从缓冲区取出产品。我们可以使用 wait()
和 notify()
或现代的 BlockingQueue
来实现这个模型。
实现思路:
- 生产者生产物品并放入缓冲区。
- 消费者从缓冲区消费物品。
- 使用
BlockingQueue
简化缓冲区管理,它已经处理了线程同步。
示例:
java
import java.util.concurrent.*;
public class ProducerConsumer {
private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
public static void main(String[] args) {
Runnable producer = () -> {
try {
for (int i = 1; i <= 10; i++) {
queue.put(i); // 阻塞式插入
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Runnable consumer = () -> {
try {
for (int i = 1; i <= 10; i++) {
Integer item = queue.take(); // 阻塞式取出
System.out.println("Consumed: " + item);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}
输出:
makefile
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
...
3. Spring IoC(控制反转)
Spring IoC 是 Spring 框架的核心概念之一。它通过依赖注入(DI)管理对象的生命周期和依赖关系。IoC 容器负责创建和管理对象,并通过构造函数、Setter 方法或者字段注入来提供依赖。
工作原理:
- Spring IoC 容器负责创建对象,并将它们注入到需要的地方。
- 容器根据配置文件或注解扫描(如
@Component
)来管理对象。
示例:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@Component
class Service {
public void serve() {
System.out.println("Service is serving...");
}
}
@Component
class Client {
private final Service service;
@Autowired
public Client(Service service) {
this.service = service;
}
public void execute() {
service.serve();
}
}
public class SpringIoCDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
Client client = context.getBean(Client.class);
client.execute();
}
}
总结:Spring IoC 通过容器管理 Bean 的生命周期和依赖关系,提供了灵活的依赖注入功能。
4. Spring AOP(面向切面编程)
Spring AOP 使得在不修改原有业务逻辑的情况下,增强或修改方法的执行过程。它可以实现诸如日志记录、事务管理等功能。AOP 中最核心的概念包括 切面 (Aspect)、连接点 (Join Point)、通知(Advice)等。
实现原理:
- AOP 通过代理模式实现。Spring 默认使用动态代理(JDK 动态代理或 CGLIB)来实现 AOP。
- 使用
@Aspect
注解定义切面,并使用@Before
、@After
等注解定义通知。
示例:
java
import org.aspectj.lang.annotation.*;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.Service.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}
@After("execution(* com.example.Service.*(..))")
public void logAfter() {
System.out.println("Logging after method execution");
}
}
@Component
class Service {
public void serve() {
System.out.println("Service is serving...");
}
}
public class SpringAOPDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
Service service = context.getBean(Service.class);
service.serve();
}
}
输出:
sql
Logging before method execution
Service is serving...
Logging after method execution
总结:Spring AOP 通过切面编程的方式解耦了横切关注点,如日志记录、安全控制等。
5. Spring Bean 生命周期
Spring Bean 生命周期包括实例化、依赖注入、初始化、销毁等阶段。Spring 容器在管理 Bean 时,会通过配置文件或注解定义 Bean 的生命周期。
关键步骤:
- 初始化 :Bean 实例化后,容器会调用初始化方法,可以通过
@PostConstruct
注解或实现InitializingBean
接口。 - 销毁 :在容器关闭时,Spring 会调用销毁方法,可以通过
@PreDestroy
注解或实现DisposableBean
接口。
示例:
java
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
class MyBean {
@PostConstruct
public void init() {
System.out.println("Bean initialized");
}
@PreDestroy
public void destroy() {
System.out.println("Bean destroyed");
}
}
public class SpringBeanLifecycle {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyBean.class);
MyBean bean = context.getBean(MyBean.class);
((AnnotationConfigApplicationContext) context).close();
}
}
输出:
Bean initialized
Bean destroyed
6. Spring 事务控制
Spring 事务管理提供了声明式和编程式两种方式来控制事务。最常用的是声明式事务,通过 @Transactional
注解来控制事务。
基本概念:
- 传播行为:定义了事务在多个方法调用之间的传播规则。
- 隔离级别:定义了事务之间如何相互隔离,避免事务干扰。
- 回滚规则:设置在出现哪些异常时回滚事务。
示例:
java
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
@Transactional
public void performTransaction() {
// 业务逻辑
}
}
总结 :Spring 通过 @Transactional
注解实现声明式事务管理,简化了事务控制。
7. **Spring 事务传播机制
Spring 事务传播机制是指在多个事务方法之间,如何处理事务的传播行为。在分布式系统或复杂的业务场景中,一个方法可能会调用另一个方法,而这些方法可能会在不同的事务中运行。Spring 提供了多种事务传播机制来控制事务在方法调用之间的传播方式。
Spring 的事务传播机制通过 @Transactional
注解来指定。常见的事务传播行为包括:REQUIRED
、REQUIRES_NEW
、SUPPORTS
、NOT_SUPPORTED
、MANDATORY
、NEVER
、NESTED
等。
下面我们将一一介绍这些传播行为,并通过示例代码来加深理解。
1. REQUIRED
(默认传播行为)
- 描述:如果当前方法已经存在事务,那么该方法将在当前事务中运行;如果当前没有事务,那么 Spring 会新建一个事务。
- 使用场景:这是最常用的传播行为,适用于大多数业务场景。
示例:
java
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 当前方法内运行在一个事务中
methodB(); // 调用方法B,若methodB已标注@Transactional,则执行时在同一事务中
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 运行在一个事务中
}
2. REQUIRES_NEW
- 描述 :每次调用该方法时,都会创建一个新的事务。如果当前有事务存在,当前事务会被挂起,待
REQUIRES_NEW
的事务提交后,挂起的事务再恢复。 - 使用场景 :当需要独立处理某些操作的事务,而不希望它们影响主事务时,使用
REQUIRES_NEW
。
示例:
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodA() {
// 这里开启一个新的事务
methodB(); // methodB 运行在一个新的事务中
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 这里运行在新的事务中,不会影响 methodA 的事务
}
3. SUPPORTS
- 描述:如果当前存在事务,则加入该事务中;如果当前没有事务,则以非事务方式执行。
- 使用场景:当某些操作可以选择性地参与事务时使用,例如一些查询操作,它们不一定需要事务,但如果有事务存在,就应该在其中执行。
示例:
java
@Transactional(propagation = Propagation.SUPPORTS)
public void methodA() {
// 如果当前有事务,加入事务;没有事务,则以非事务方式执行
}
4. NOT_SUPPORTED
- 描述:如果当前有事务存在,则挂起该事务,方法会以非事务方式执行。如果当前没有事务,方法会继续执行。
- 使用场景:适用于那些不能在事务中执行的操作,比如一些查询操作可能会依赖事务外部的资源。
示例:
java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodA() {
// 如果当前有事务,则挂起该事务,非事务方式执行
}
5. MANDATORY
- 描述:该方法必须在一个已存在的事务中执行。如果当前没有事务,则会抛出异常。
- 使用场景:当某些方法必须在事务中执行时,例如一些需要确保数据一致性的操作。
示例:
java
@Transactional(propagation = Propagation.MANDATORY)
public void methodA() {
// 当前方法必须在事务中执行,如果没有事务会抛出异常
}
6. NEVER
- 描述:该方法不能在事务中执行。如果当前有事务,则会抛出异常。
- 使用场景:用于那些必须在非事务环境下执行的操作,例如某些资源释放操作。
示例:
java
@Transactional(propagation = Propagation.NEVER)
public void methodA() {
// 当前方法不能在事务中执行,如果有事务则抛出异常
}
7. NESTED
- 描述 :如果当前存在事务,则创建一个嵌套事务(类似子事务)。如果当前没有事务,则等同于
REQUIRED
。 - 使用场景:当需要嵌套事务时使用,如果外部事务失败,则嵌套事务也会回滚。
示例:
java
@Transactional(propagation = Propagation.NESTED)
public void methodA() {
// 如果存在事务,创建一个嵌套事务
methodB(); // methodB 运行在嵌套事务中
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 运行在嵌套事务中
}
8. 事务传播机制总结
传播行为 | 描述 | 使用场景 |
---|---|---|
REQUIRED |
如果有事务,加入当前事务;否则创建新事务。 | 默认的传播行为,常用。 |
REQUIRES_NEW |
总是创建新事务,挂起当前事务。 | 需要独立事务时使用,避免当前事务影响。 |
SUPPORTS |
如果有事务,加入当前事务;如果没有事务,以非事务方式执行。 | 非必须在事务中执行的操作。 |
NOT_SUPPORTED |
挂起当前事务,非事务方式执行。 | 不支持在事务中执行的操作。 |
MANDATORY |
必须在事务中执行,若没有事务则抛出异常。 | 必须在事务环境下执行的操作。 |
NEVER |
必须在非事务中执行,若有事务则抛出异常。 | 必须在非事务环境下执行的操作。 |
NESTED |
在当前事务中创建嵌套事务,如果没有事务,则和 REQUIRED 一样。 |
需要嵌套事务的场景。 |
9. 事务传播机制的应用场景
REQUIRED
:适用于绝大部分场景,多个方法需要共同完成一项任务时使用。REQUIRES_NEW
:用于需要独立事务的场景,如处理日志记录、发送邮件等操作。SUPPORTS
:用于不要求事务的查询操作。NOT_SUPPORTED
:用于不允许在事务中的操作。MANDATORY
:当你需要保证方法必须在事务中运行时使用,确保数据的一致性。NEVER
:用于必须在非事务环境下执行的操作。NESTED
:用于需要子事务的复杂操作,确保主事务失败时回滚嵌套事务。
通过合理选择合适的事务传播行为,可以确保系统中的数据一致性、隔离性以及性能优化。