DeepSeek Spring Controller 线程安全面试题及答案
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线程安全的各个方面,从基础概念到高级实践,希望能帮助你全面掌握这一重要主题。
更多推荐

所有评论(0)