Spring Controller 线程安全面试题及答案

一、基础概念题(1-15)

1. 什么是线程安全?

答案:线程安全指在多线程环境下,多个线程访问同一个对象或方法时,不需要额外的同步操作,仍能保证数据的一致性和正确性。在Spring Controller中,线程安全意味着Controller实例能够正确处理并发请求而不会产生数据混乱。

2. Spring默认的Controller是单例还是多例?

答案:Spring默认情况下,Controller是单例的(Singleton)。Spring容器只会为每个Controller创建一个实例,所有请求共享这个实例。

3. 单例Controller在多线程环境下可能出现什么问题?

答案:

· 如果Controller包含实例变量(非静态字段),多个线程可能同时修改这些变量
· 可能导致数据污染、竞态条件
· 一个用户的数据可能被另一个用户看到

4. 如何将Controller配置为原型(Prototype)作用域?

答案:

```java
@Controller
@Scope("prototype")
public class MyController {
    // 每次请求都会创建新的实例
}
```

5. 原型作用域的Controller有什么优缺点?

答案:
优点:线程安全,每个请求有独立实例
缺点:性能开销大,内存消耗增加,不适合高并发场景

6. @Controller和@RestController在线程安全方面有区别吗?

答案:没有本质区别。两者都是Spring MVC的组件,默认都是单例的。@RestController只是@Controller和@ResponseBody的组合。

7. 什么是无状态(Stateless)设计?

答案:无状态设计指组件不保存任何与特定请求相关的状态信息。所有请求的数据都通过参数传递,这样实例就可以安全地在多个线程间共享。

8. Spring MVC的哪些组件默认是线程安全的?

答案:

· HandlerMapping
· HandlerAdapter
· ViewResolver
· 无状态的@Controller(不包含实例变量)
· Spring管理的Bean(如果设计为无状态)

9. 为什么Spring默认使用单例模式?

答案:

· 减少内存开销
· 提高性能(减少对象创建和垃圾回收)
· 符合大多数Web应用的需求(Controller通常是无状态的)

10. 解释ThreadLocal在Controller线程安全中的作用

答案:ThreadLocal为每个线程提供独立的变量副本,避免线程间共享变量。在Controller中可用于存储用户特定的数据(如当前用户信息)。

11. 什么是竞态条件(Race Condition)?

答案:当多个线程同时访问共享数据,且至少有一个线程在修改数据时,最终结果依赖于线程执行的顺序,这种情况称为竞态条件。

12. 什么是原子性操作?

答案:原子性操作是不可中断的操作,要么完全执行,要么完全不执行。在并发环境下,原子操作不需要额外的同步机制。

13. volatile关键字能保证线程安全吗?

答案:不能完全保证。volatile只能保证变量的可见性和禁止指令重排序,但不能保证复合操作的原子性。

14. 简单描述Spring的Bean作用域

答案:

· singleton:默认,每个容器一个实例
· prototype:每次请求创建新实例
· request:每个HTTP请求一个实例
· session:每个HTTP会话一个实例
· application:每个ServletContext一个实例
· websocket:每个WebSocket会话一个实例

15. @RequestScope和@SessionScope注解的作用

答案:

· @RequestScope:Bean在HTTP请求期间有效
· @SessionScope:Bean在HTTP会话期间有效

二、实例变量和状态管理(16-30)

16. 以下代码是否线程安全?为什么?

```java
@RestController
public class UnsafeController {
    private int count = 0;
    
    @GetMapping("/increment")
    public int increment() {
        return count++;
    }
}
```

答案:不安全。count++不是原子操作,多个线程同时执行会导致数据丢失和不一致。

17. 如何修复上题的线程安全问题?

答案:

```java
@RestController
public class SafeController {
    private final AtomicInteger count = new AtomicInteger(0);
    
    @GetMapping("/increment")
    public int increment() {
        return count.incrementAndGet();
    }
}
```

18. Controller中的SimpleDateFormat为什么会导致线程安全问题?

答案:SimpleDateFormat不是线程安全的,它内部维护了日历对象,多个线程同时调用format()或parse()方法会修改共享的日历状态。

19. 如何安全地使用SimpleDateFormat?

答案:

```java
// 方法1:使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 方法2:每次创建新实例(性能较差)
// 方法3:使用DateTimeFormatter(Java 8+,线程安全)
```

20. Controller中存放用户列表是否安全?

```java
@RestController
public class UserController {
    private List<String> users = new ArrayList<>();
    
    @PostMapping("/add")
    public void addUser(String user) {
        users.add(user);
    }
}
```

答案:不安全。ArrayList不是线程安全的,多个线程同时添加元素可能导致数据损坏或ConcurrentModificationException。

21. 如何安全地在Controller中共享集合?

答案:

```java
@RestController
public class SafeUserController {
    // 使用并发集合
    private final ConcurrentMap<String, String> users = new ConcurrentHashMap<>();
    private final List<String> safeList = Collections.synchronizedList(new ArrayList<>());
    private final CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>();
}
```

22. @Autowired注入的Service是线程安全的吗?

答案:取决于Service的实现。如果Service是无状态的(没有实例变量或只有final变量),通常是线程安全的。如果有状态,需要额外的同步机制。

23. 静态变量在Controller中是否安全?

答案:通常不安全。静态变量被所有Controller实例共享,多个线程同时访问需要同步控制。

24. 以下代码有什么问题?

```java
@RestController
public class ProblemController {
    private Map<String, Object> cache = new HashMap<>();
    
    @GetMapping("/get/{key}")
    public Object get(@PathVariable String key) {
        if (!cache.containsKey(key)) {
            cache.put(key, expensiveOperation(key));
        }
        return cache.get(key);
    }
}
```

答案:

1. HashMap不是线程安全的
2. 存在"先检查后执行"的竞态条件
3. 可能导致expensiveOperation被多次执行

25. 如何实现线程安全的缓存?

答案:

```java
@RestController
public class SafeCacheController {
    private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<>();
    
    @GetMapping("/get/{key}")
    public Object get(@PathVariable String key) {
        return cache.computeIfAbsent(key, this::expensiveOperation);
    }
}
```

26. 什么是内存可见性问题?

答案:当一个线程修改了共享变量的值,其他线程可能无法立即看到这个修改,因为变量可能被缓存在CPU寄存器或缓存中。

27. final关键字如何帮助实现线程安全?

答案:

· final变量在构造完成后不可变
· 正确构造的final对象对其他线程是可见的
· 可以安全地发布而不需要同步

28. 什么是安全发布模式?

答案:安全发布确保对象对所有访问它的线程都是可见的,并且对象的状态不会被意外修改。方式包括:

· 通过静态初始化器
· 将引用存储到volatile或AtomicReference
· 将引用存储到正确创建的final字段
· 通过锁保护

29. Controller中能否使用synchronized方法?

答案:可以,但不推荐。synchronized方法会降低并发性能,可能导致瓶颈。更好的做法是使用并发工具类或无状态设计。

30. 双重检查锁定(Double-Checked Locking)的问题和解决方案

答案:

```java
// 有问题版本
if (instance == null) {
    synchronized (Singleton.class) {
        if (instance == null) {
            instance = new Singleton();
        }
    }
}

// 正确版本(Java 5+)
private volatile static Singleton instance;

// 或者使用静态内部类
private static class Holder {
    static final Singleton INSTANCE = new Singleton();
}
```

三、作用域和生命周期(31-45)

31. @Scope("request")和@Scope("session")的区别

答案:

· request:Bean的生命周期与HTTP请求相同,每个请求创建新实例
· session:Bean的生命周期与HTTP会话相同,每个用户会话创建新实例

32. 如何在单例Bean中注入request作用域的Bean?

答案:使用代理模式

```java
@Component
public class SingletonBean {
    @Autowired
    private HttpServletRequest request;  // 通过代理注入
    
    @Autowired
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    private RequestScopedBean requestBean;
}
```

33. ScopedProxyMode的作用是什么?

答案:创建代理对象来延迟获取实际的作用域Bean。当调用代理对象的方法时,代理会从当前作用域获取真实对象并调用相应方法。

34. 以下配置有什么问题?

```java
@Controller
@Scope("session")
public class UserSessionController {
    private List<String> userActions = new ArrayList<>();
}
```

答案:如果用户量很大,会创建大量session作用域的Bean,消耗大量内存。应该考虑使用其他方式管理会话数据。

35. 如何在Spring Boot中配置全局的作用域代理?

答案:

```properties
# application.properties
spring.aop.proxy-target-class=true
```

36. WebApplicationContext和ApplicationContext的区别

答案:WebApplicationContext是ApplicationContext的Web扩展,支持request、session等Web作用域。

37. 什么是作用域代理的CGLIB和JDK动态代理?

答案:

· CGLIB代理:通过子类化创建代理,不需要接口
· JDK动态代理:基于接口创建代理
· ScopedProxyMode.TARGET_CLASS使用CGLIB,ScopedProxyMode.INTERFACES使用JDK代理

38. 如何在过滤器中访问request作用域的Bean?

答案:需要确保请求已经绑定到当前线程

```java
@Autowired
private RequestContextListener requestContextListener;

// 或者通过RequestContextHolder
ServletRequestAttributes attributes = 
    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
```

39. @ModelAttribute方法是否线程安全?

答案:取决于方法实现。如果方法使用实例变量或修改共享状态,可能不安全。通常@ModelAttribute方法应该是无状态的。

40. 拦截器(Interceptor)中的线程安全问题

答案:拦截器默认是单例的,应该设计为无状态。如果需要在拦截器中存储请求特定数据,可以使用ThreadLocal或request属性。

41. 如何清理ThreadLocal资源?

答案:

```java
@RestController
public class ThreadLocalController {
    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
    
    @GetMapping("/user")
    public User getUser() {
        try {
            return currentUser.get();
        } finally {
            currentUser.remove();  // 重要:防止内存泄漏
        }
    }
}
```

42. 异步请求处理中的线程安全问题

答案:@Async方法或DeferredResult/Callable可能在工作线程中执行,不能直接访问request/session作用域的Bean,需要通过RequestContextHolder传递上下文。

43. Spring Security上下文是线程安全的吗?

答案:SecurityContext默认存储在ThreadLocal中,每个线程有独立副本。但在异步任务中需要手动传递SecurityContext。

44. 如何在@Async方法中传递安全上下文?

答案:

```java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextExecutorService(
            Executors.newFixedThreadPool(10)
        );
    }
}
```

45. 什么是线程局部变量泄漏?

答案:当使用线程池时,线程会被重用。如果ThreadLocal变量没有及时清理,前一个请求的数据可能泄漏到后一个请求中。

四、并发工具和最佳实践(46-60)

46. 什么时候使用AtomicInteger而不是synchronized?

答案:

· AtomicInteger适合简单的原子操作(递增、比较并交换)
· synchronized适合复杂的同步块
· AtomicInteger通常性能更好,使用CAS(Compare-And-Swap)操作

47. ConcurrentHashMap和Collections.synchronizedMap的区别

答案:

· synchronizedMap:包装普通Map,所有方法同步,性能较差
· ConcurrentHashMap:分段锁或CAS,支持更高并发,迭代时不抛出ConcurrentModificationException

48. CopyOnWriteArrayList适用于什么场景?

答案:读多写少的场景。每次修改都创建底层数组的新副本,读操作不需要同步,写操作性能较差。

49. 如何实现一个线程安全的计数器?

答案:

```java
@RestController
public class CounterController {
    // 方法1:使用AtomicLong
    private final AtomicLong counter1 = new AtomicLong();
    
    // 方法2:使用LongAdder(高并发下性能更好)
    private final LongAdder counter2 = new LongAdder();
    
    // 方法3:使用synchronized(最简单但性能较差)
    private long counter3 = 0;
    public synchronized void increment3() { counter3++; }
}
```

50. CountDownLatch在Controller测试中的应用

答案:用于等待多个异步操作完成

```java
@Test
public void testConcurrentRequests() throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(100);
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            // 调用Controller方法
            latch.countDown();
        }).start();
    }
    latch.await();  // 等待所有线程完成
    // 验证结果
}
```

51. CyclicBarrier和CountDownLatch的区别

答案:

· CountDownLatch:一次性使用,线程等待事件发生
· CyclicBarrier:可重复使用,线程相互等待
· 在Controller测试中,CountDownLatch更常用

52. Semaphore在限流中的应用

答案:

```java
@RestController
public class RateLimitedController {
    private final Semaphore semaphore = new Semaphore(10); // 最大10个并发
    
    @GetMapping("/limited")
    public String limitedEndpoint() throws InterruptedException {
        if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) {
            try {
                // 处理请求
                return "Success";
            } finally {
                semaphore.release();
            }
        }
        return "Too many requests";
    }
}
```

53. CompletableFuture在异步Controller中的应用

答案:

```java
@RestController
public class AsyncController {
    @GetMapping("/async")
    public CompletableFuture<String> asyncMethod() {
        return CompletableFuture.supplyAsync(() -> {
            // 长时间运行的任务
            return "Result";
        });
    }
}
```

54. @ControllerAdvice中的线程安全问题

答案:@ControllerAdvice默认是单例的,应该设计为无状态。如果需要处理异常时的特定请求数据,可以从参数中获取。

55. 如何实现线程安全的日期格式化?

答案:

```java
// Java 8+
private static final DateTimeFormatter formatter = 
    DateTimeFormatter.ofPattern("yyyy-MM-dd");

// Java 7及以下
private static final ThreadLocal<SimpleDateFormat> threadLocalFormatter =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
```

56. 什么是不可变对象?为什么它有助于线程安全?

答案:不可变对象在创建后状态不能修改。因为不可变,所以可以安全地在多个线程间共享,不需要同步。

57. 如何创建不可变的Controller辅助类?

答案:

```java
// 1. 类声明为final
// 2. 所有字段声明为private final
// 3. 不提供setter方法
// 4. 如果字段是可变对象,返回防御性副本
public final class ImmutableHelper {
    private final String name;
    private final List<String> items;
    
    public ImmutableHelper(String name, List<String> items) {
        this.name = name;
        this.items = Collections.unmodifiableList(new ArrayList<>(items));
    }
    
    public List<String> getItems() {
        return Collections.unmodifiableList(items);
    }
}
```

58. Spring的@Cacheable注解是否线程安全?

答案:取决于底层缓存实现。Spring默认使用ConcurrentMapCache,它是线程安全的。但需要注意缓存的键值对象应该是不可变的或线程安全的。

59. 如何测试Controller的线程安全性?

答案:

```java
@Test
public void testThreadSafety() throws Exception {
    int threadCount = 100;
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    CountDownLatch latch = new CountDownLatch(threadCount);
    
    List<Future<Integer>> futures = new ArrayList<>();
    
    for (int i = 0; i < threadCount; i++) {
        futures.add(executor.submit(() -> {
            latch.countDown();
            latch.await();
            // 调用Controller方法
            return 1;
        }));
    }
    
    // 验证结果
    int sum = futures.stream().mapToInt(f -> {
        try { return f.get(); }
        catch (Exception e) { return 0; }
    }).sum();
    
    assertEquals(threadCount, sum);
}
```

60. 什么是ABA问题?

答案:在CAS操作中,变量从A变为B又变回A,CAS会认为没有变化。AtomicStampedReference可以解决这个问题。

五、高级主题和框架集成(61-75)

61. Spring WebFlux和传统Spring MVC在线程模型上的区别

答案:

· Spring MVC:基于Servlet API,每个请求一个线程(线程池)
· WebFlux:基于Reactive Streams,事件驱动,少量线程处理大量请求
· WebFlux更高效,但编程模型不同

62. Reactive Controller是否还有线程安全问题?

答案:在WebFlux中,Controller方法返回Mono/Flux,执行在不同的线程上。但响应式编程鼓励无状态设计,减少了线程安全问题。

63. 如何在Spring Security中实现线程安全的权限检查?

答案:

```java
@RestController
public class SecureController {
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/admin")
    public String adminOnly() {
        return "Admin area";
    }
    
    @PostAuthorize("returnObject.owner == authentication.name")
    @GetMapping("/resource/{id}")
    public Resource getResource(@PathVariable String id) {
        // 获取资源
    }
}
```

64. @Transactional在Controller中使用是否安全?

答案:不推荐在Controller中使用@Transactional。事务应该放在Service层。如果在Controller使用,需要注意:

1. 可能打开事务时间过长
2. 可能在不同的方法调用中创建多个事务
3. 异常处理复杂

65. 分布式环境下的线程安全考虑

答案:

· 不能依赖内存同步(不同JVM)
· 需要使用分布式锁(Redis、ZooKeeper)
· 考虑最终一致性而非强一致性
· 使用分布式Session或JWT

66. Spring Session如何解决集群环境下的Session问题?

答案:Spring Session将会话数据存储到外部存储(Redis、JDBC、MongoDB),多个应用实例共享会话数据。

67. 什么是CQRS模式?如何帮助线程安全?

答案:命令查询职责分离。将写操作(命令)和读操作(查询)分离,使用不同的模型。可以减少共享状态,提高并发性。

68. Event Sourcing对线程安全的影响

答案:事件溯源通过保存状态变化事件而不是当前状态来实现。写操作追加事件,读操作重放事件。减少并发冲突,但增加了复杂性。

69. 如何在微服务架构中保证Controller线程安全?

答案:

1. 每个服务无状态设计
2. 使用API Gateway处理跨服务问题
3. 分布式锁用于跨服务协调
4. 重试和熔断机制处理失败

70. Spring Cloud Sleuth中的线程上下文传递

答案:Sleuth通过TraceId和SpanId跟踪请求链。在异步操作中需要手动传递跟踪上下文:

```java
@Autowired
private Tracer tracer;

@Async
public CompletableFuture<Void> asyncMethod() {
    Span span = tracer.nextSpan().name("async").start();
    try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
        // 异步操作
    } finally {
        span.finish();
    }
}
```

71. 如何监控Controller的线程安全问题?

答案:

1. 使用Thread Dump分析线程状态
2. 使用APM工具(AppDynamics、New Relic)
3. 监控线程池使用情况
4. 日志中记录ThreadLocal信息
5. 压力测试发现竞态条件

72. 什么是Happens-Before关系?

答案:Java内存模型定义的内存可见性保证。如果一个操作happens-before另一个操作,那么第一个操作的结果对第二个操作可见。

73. volatile的happens-before规则

答案:对volatile变量的写操作happens-before后续对同一变量的读操作。

74. synchronized的happens-before规则

答案:监视器锁的解锁happens-before后续对同一监视器锁的加锁。

75. final字段的happens-before规则

答案:构造函数中对final字段的写入,以及通过这个引用可以到达的任何变量的写入,happens-before读取这个引用。

六、实战案例和问题解决(76-90)

76. 用户反馈看到他人数据,如何排查?

答案:

1. 检查Controller是否有实例变量
2. 检查是否错误使用了ThreadLocal(没有清理)
3. 检查缓存键是否包含用户标识
4. 检查Session管理是否正确
5. 使用用户特定的日志跟踪请求

77. 高并发下计数器不准确,如何解决?

答案:

```java
@RestController
public class AccurateCounterController {
    // 方案1:使用LongAdder(JDK8+)
    private final LongAdder adder = new LongAdder();
    
    @GetMapping("/increment")
    public long increment() {
        adder.increment();
        return adder.sum();
    }
    
    // 方案2:使用Redis分布式计数器(集群环境)
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    public long distributedIncrement() {
        return redisTemplate.opsForValue().increment("counter");
    }
}
```

78. 如何设计线程安全的文件上传Controller?

答案:

```java
@RestController
public class FileUploadController {
    // 使用UUID作为文件名避免冲突
    private String generateFileName(String originalName) {
        return UUID.randomUUID() + "_" + originalName;
    }
    
    // 使用同步或分布式锁处理同名文件
    private final Object fileLock = new Object();
    
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) {
        String filename = generateFileName(file.getOriginalFilename());
        
        synchronized (fileLock) {
            // 检查文件是否存在
            if (Files.exists(Paths.get(filename))) {
                throw new RuntimeException("File already exists");
            }
            // 保存文件
            file.transferTo(new File(filename));
        }
        return "Success";
    }
}
```

79. 如何实现线程安全的ID生成器?

答案:

```java
@Component
public class IdGenerator {
    // 雪花算法(Snowflake) - 分布式ID生成
    private final long datacenterId;
    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & 4095;  // 12位序列号
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = timestamp;
        
        return ((timestamp - 1288834974657L) << 22) |
                (datacenterId << 17) |
                (workerId << 12) |
                sequence;
    }
}
```

80. 如何避免Controller中的NullPointerException?

答案:

```java
@RestController
public class SafeController {
    // 1. 使用Optional
    @GetMapping("/user/{id}")
    public ResponseEntity<User> getUser(@PathVariable String id) {
        return userService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    // 2. 使用@NonNull注解
    public void process(@NonNull String input) {
        // input不会为null
    }
    
    // 3. 使用Objects.requireNonNull
    public void process(String input) {
        Objects.requireNonNull(input, "Input cannot be null");
    }
}
```

81. 如何处理Controller中的并发修改异常?

答案:

```java
@RestController
public class ConcurrentModificationSafeController {
    private final List<String> items = new CopyOnWriteArrayList<>();
    
    @GetMapping("/items")
    public List<String> getItems() {
        // CopyOnWriteArrayList允许在迭代时修改
        return new ArrayList<>(items);
    }
    
    @PostMapping("/items")
    public void addItem(@RequestBody String item) {
        items.add(item);
    }
    
    // 或者使用显式锁
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public void safeMethod() {
        lock.writeLock().lock();
        try {
            // 修改操作
        } finally {
            lock.writeLock().unlock();
        }
    }
}
```

82. 如何设计线程安全的配置管理器?

答案:

```java
@Component
@RefreshScope  // Spring Cloud Config支持动态刷新
public class ConfigManager {
    private volatile Map<String, String> config = new HashMap<>();
    
    // 使用双重检查锁定更新配置
    public void updateConfig(Map<String, String> newConfig) {
        Map<String, String> temp = new HashMap<>(newConfig);
        config = temp;  // volatile保证可见性
    }
    
    public String getConfig(String key) {
        return config.get(key);  // 线程安全的读操作
    }
}
```

83. 如何在Controller中安全地使用缓存?

答案:

```java
@RestController
@CacheConfig(cacheNames = "users")
public class CachingController {
    @Autowired
    private CacheManager cacheManager;
    
    @GetMapping("/user/{id}")
    @Cacheable(key = "#id", unless = "#result == null")
    public User getUser(@PathVariable String id) {
        // 数据库查询
    }
    
    @PutMapping("/user/{id}")
    @CachePut(key = "#id")
    public User updateUser(@PathVariable String id, @RequestBody User user) {
        // 更新操作
        return user;
    }
    
    @DeleteMapping("/user/{id}")
    @CacheEvict(key = "#id")
    public void deleteUser(@PathVariable String id) {
        // 删除操作
    }
}
```

84. 如何实现请求限流和防刷?

答案:

```java
@RestController
public class RateLimitController {
    // 使用Guava RateLimiter
    private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 10 QPS
    
    @GetMapping("/limited")
    public String limited() {
        if (!rateLimiter.tryAcquire()) {
            throw new TooManyRequestsException();
        }
        return "Success";
    }
    
    // 或者使用Spring Cloud Gateway的限流
    // 或者使用Redis实现分布式限流
}
```

85. 如何处理Controller中的事务和线程安全?

答案:

```java
@RestController
public class TransactionalController {
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @GetMapping("/transfer")
    public String transfer(@RequestParam String from, 
                          @RequestParam String to,
                          @RequestParam BigDecimal amount) {
        // 使用编程式事务控制
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition()
        );
        
        try {
            // 业务逻辑
            accountService.transfer(from, to, amount);
            transactionManager.commit(status);
            return "Success";
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}
```

86. 如何调试线程死锁问题?

答案:

1. 使用jstack生成线程转储
2. 使用JConsole或VisualVM监控线程
3. 查找BLOCKED状态的线程
4. 检查锁的获取顺序
5. 使用@Controller日志记录锁信息

```java
@RestController
public class DeadLockAvoidController {
    // 使用锁顺序约定避免死锁
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {
                // 操作
            }
        }
    }
    
    public void method2() {
        synchronized (lock1) {  // 相同的顺序
            synchronized (lock2) {
                // 操作
            }
        }
    }
}
```

87. 如何实现线程安全的单例模式?

答案:

```java
// 方法1:枚举(最佳实践)
public enum Singleton {
    INSTANCE;
    public void doSomething() {}
}

// 方法2:静态内部类
public class Singleton {
    private Singleton() {}
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

// 方法3:双重检查锁定(Java 5+)
public class Singleton {
    private volatile static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
```

88. 如何设计线程安全的API版本管理?

答案:

```java
@RestController
@RequestMapping("/api/v{version}")
public class VersionedController {
    // 使用ConcurrentHashMap存储不同版本的处理器
    private final ConcurrentMap<String, Function<Request, Response>> handlers = 
        new ConcurrentHashMap<>();
    
    @PostConstruct
    public void init() {
        handlers.put("1.0", this::handleV1);
        handlers.put("2.0", this::handleV2);
    }
    
    @GetMapping("/users/{id}")
    public ResponseEntity<?> getUser(
            @PathVariable String version,
            @PathVariable String id) {
        Function<Request, Response> handler = handlers.get(version);
        if (handler == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
        return ResponseEntity.ok(handler.apply(new Request(id)));
    }
    
    // 动态添加新版本(线程安全)
    public void addVersionHandler(String version, Function<Request, Response> handler) {
        handlers.put(version, handler);
    }
}
```

89. 如何处理Controller中的资源泄漏?

答案:

```java
@RestController
public class ResourceSafeController {
    // 使用try-with-resources
    @GetMapping("/file/{name}")
    public ResponseEntity<Resource> getFile(@PathVariable String name) throws IOException {
        Path path = Paths.get(name);
        
        try (InputStream is = Files.newInputStream(path);
             BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
             
            String content = reader.lines().collect(Collectors.joining("\n"));
            return ResponseEntity.ok(new ByteArrayResource(content.getBytes()));
        }
    }
    
    // 使用@PreDestroy清理资源
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    @PreDestroy
    public void cleanup() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
```

90. 如何实现线程安全的事件发布器?

答案:

```java
@Component
public class EventPublisher {
    // 使用CopyOnWriteArrayList存储监听器
    private final List<ApplicationListener<CustomEvent>> listeners = 
        new CopyOnWriteArrayList<>();
    
    // 注册监听器(线程安全)
    public void registerListener(ApplicationListener<CustomEvent> listener) {
        listeners.add(listener);
    }
    
    // 发布事件(线程安全)
    public void publishEvent(CustomEvent event) {
        for (ApplicationListener<CustomEvent> listener : listeners) {
            try {
                listener.onApplicationEvent(event);
            } catch (Exception e) {
                // 记录日志但不影响其他监听器
                log.error("Listener failed", e);
            }
        }
    }
}
```

七、性能优化和最佳实践(91-100)

91. 如何优化Controller的并发性能?

答案:

1. 使用无状态设计
2. 合理使用线程池(避免过大或过小)
3. 使用异步处理(@Async、CompletableFuture)
4. 缓存频繁访问的数据
5. 使用连接池(数据库、HTTP客户端)
6. 避免在Controller中进行复杂计算
7. 使用响应式编程(WebFlux)

92. 线程池配置的最佳实践

答案:

```java
@Configuration
public class ThreadPoolConfig {
    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);          // 核心线程数
        executor.setMaxPoolSize(50);           // 最大线程数
        executor.setQueueCapacity(100);        // 队列容量
        executor.setKeepAliveSeconds(60);      // 空闲线程存活时间
        executor.setThreadNamePrefix("controller-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
```

93. 如何选择合适的数据结构?

答案:

· 读多写少:CopyOnWriteArrayList、ConcurrentHashMap
· 写多读少:Collections.synchronizedList、普通集合+锁
· 高并发计数器:LongAdder、AtomicLong
· 缓存:Caffeine、Guava Cache、Redis

94. 避免过度同步的策略

答案:

1. 缩小同步范围(同步块而不是方法)
2. 使用读写锁(ReadWriteLock)
3. 使用不可变对象
4. 使用并发集合
5. 使用原子变量

95. 如何设计线程安全的DTO?

答案:

```java
// 使用不可变DTO
public final class UserDTO {
    private final String id;
    private final String name;
    private final List<String> roles;  // 不可变列表
    
    public UserDTO(String id, String name, List<String> roles) {
        this.id = id;
        this.name = name;
        this.roles = Collections.unmodifiableList(new ArrayList<>(roles));
    }
    
    // 只提供getter方法
    public List<String> getRoles() {
        return roles;
    }
    
    // Builder模式
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder {
        private String id;
        private String name;
        private List<String> roles = new ArrayList<>();
        
        public Builder id(String id) { this.id = id; return this; }
        public Builder name(String name) { this.name = name; return this; }
        public Builder role(String role) { this.roles.add(role); return this; }
        
        public UserDTO build() {
            return new UserDTO(id, name, roles);
        }
    }
}
```

96. 监控和诊断线程安全问题的工具

答案:

1. JVM工具:jstack、jconsole、jvisualvm、jstat
2. APM工具:AppDynamics、New Relic、Dynatrace
3. 日志框架:MDC(Mapped Diagnostic Context)记录线程信息
4. 测试工具:JMeter、Gatling进行压力测试
5. 代码分析:FindBugs、SonarQube、SpotBugs

97. 如何编写线程安全的单元测试?

答案:

```java
@SpringBootTest
public class ThreadSafeControllerTest {
    @Autowired
    private TestController controller;
    
    @Test
    public void testConcurrentAccess() throws Exception {
        int threadCount = 50;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        List<Future<Integer>> futures = new ArrayList<>();
        
        for (int i = 0; i < threadCount; i++) {
            futures.add(executor.submit(() -> {
                latch.countDown();
                latch.await();
                return controller.incrementAndGet();
            }));
        }
        
        Set<Integer> results = new HashSet<>();
        for (Future<Integer> future : futures) {
            results.add(future.get());
        }
        
        // 验证所有结果都是唯一的
        assertEquals(threadCount, results.size());
        
        executor.shutdown();
    }
}
```

98. Spring Boot中的线程安全配置

答案:

```yaml
# application.yml
spring:
  task:
    execution:
      pool:
        core-size: 10
        max-size: 50
        queue-capacity: 100
      thread-name-prefix: "app-task-"
      
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
      
server:
  tomcat:
    max-threads: 200
    min-spare-threads: 10
```

99. 常见的线程安全反模式

答案:

1. 单例中的可变状态:单例Bean包含实例变量
2. 不正确的双重检查锁定:没有使用volatile
3. 滥用ThreadLocal:没有及时清理导致内存泄漏
4. 过度同步:同步范围过大影响性能
5. 忽略异常处理:锁没有在finally中释放
6. 条件竞争:先检查后执行
7. 死锁:多个锁获取顺序不一致
8. 活锁:线程不断重试但无法进展

100. 总结:Controller线程安全的黄金法则

答案:

1. 尽可能无状态:不要在Controller中保存状态
2. 使用局部变量:方法内的局部变量是线程安全的
3. 如果需要状态,使用ThreadLocal:但要及时清理
4. 使用并发工具类:而不是自己实现同步
5. 优先使用不可变对象:不可变对象天然线程安全
6. 合理设计作用域:单例适合无状态,原型适合有状态
7. 测试并发场景:编写多线程测试用例
8. 监控和分析:使用工具监控线程行为
9. 保持简单:复杂的同步逻辑容易出错
10. 持续学习:了解Java内存模型和并发原理

---

八、附加资源

推荐的进一步学习资料:

1. 书籍:
   · 《Java并发编程实战》
   · 《深入理解Java虚拟机》
   · 《Spring实战》
2. 在线资源:
   · Oracle官方Java并发教程
   · Spring Framework官方文档
   · Baeldung网站上的Spring教程
3. 工具:
   · JProfiler:性能分析工具
   · YourKit:Java性能分析工具
   · VisualVM:JVM监控工具
4. 实践建议:
   · 在实际项目中实践线程安全
   · 参与开源项目学习最佳实践
   · 定期进行代码审查关注线程安全问题

这份全面的面试题及答案涵盖了Spring Controller线程安全的各个方面,从基础概念到高级实践,希望能帮助你全面掌握这一重要主题。

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐