面试真实经历某商银行大厂Java问题和答案总结(二)

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 注解来指定。常见的事务传播行为包括:REQUIREDREQUIRES_NEWSUPPORTSNOT_SUPPORTEDMANDATORYNEVERNESTED 等。

下面我们将一一介绍这些传播行为,并通过示例代码来加深理解。


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:用于需要子事务的复杂操作,确保主事务失败时回滚嵌套事务。

通过合理选择合适的事务传播行为,可以确保系统中的数据一致性、隔离性以及性能优化。

相关推荐
Postkarte不想说话3 小时前
FreeBSD配置Jails
后端
但求无bug3 小时前
Java中计算两个日期的相差时间
后端
小傅哥3 小时前
新项目完结,Ai Agent 智能体、拖拉拽编排!
前端·后端
廖广杰3 小时前
java虚拟机-如何通过GC日志判断晋升失败(Promotion Failed)
后端
自由的疯3 小时前
优雅的代码java
java·后端·面试
gensue3 小时前
【征文计划】深度解析Rokid UXR 2.0 SDK:Unity开发者的空间计算开发利器
后端
kingg3 小时前
【征文计划】基于 Rokid JSAR 的 2D 粒子画廊实现:从技术概述到核心代码解析
github
.NET修仙日记3 小时前
2025年ASP.NETMVC面试题库全解析
面试·职场和发展·c#·asp.net·mvc·面试题·asp.net mvc
召摇4 小时前
深入Next.js应用性能优化:懒加载技术全解析
前端·面试·next.js