核心概念

Spring Bean本身不是线程安全的。Spring容器只负责Bean的生命周期管理和依赖注入,不提供线程安全保障。

Bean的线程安全性取决于:

  1. Bean的作用域:singleton、prototype等
  2. Bean是否有可变状态:成员变量
  3. Bean的使用方式:是否在多线程环境下共享

简单来说:

  • 无状态Bean(没有成员变量或只有final成员变量)→ 线程安全
  • 有状态Bean(有可变成员变量)→ 需要额外处理才能保证线程安全

不同作用域下的线程安全性

1. singleton(单例)- 需要注意线程安全

特点

  • 整个应用共享一个实例
  • 多线程并发访问同一个对象
  • 默认不是线程安全的
// ❌ 线程不安全的单例Bean
@Service
public class UnsafeCounterService {
    
    private int count = 0;  // 共享可变状态
    
    public void increment() {
        count++;  // 非原子操作,线程不安全
    }
    
    public int getCount() {
        return count;
    }
}

// 测试:多线程并发调用
@SpringBootTest
public class ThreadSafetyTest {
    
    @Autowired
    private UnsafeCounterService counterService;
    
    @Test
    public void testConcurrency() throws InterruptedException {
        int threadCount = 100;
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counterService.increment();
                }
                latch.countDown();
            }).start();
        }
        
        latch.await();
        // 预期:10000,实际:可能小于10000(线程不安全)
        System.out.println("Count: " + counterService.getCount());
    }
}

2. prototype(原型)- 天然线程安全

特点

  • 每次获取都是新实例
  • 不同线程使用不同对象
  • 天然线程安全(但要注意对象本身的线程安全性)
@Service
@Scope("prototype")
public class PrototypeService {
    
    private int count = 0;  // 每个实例独立
    
    public void increment() {
        count++;  // 线程安全(不同线程使用不同实例)
    }
    
    public int getCount() {
        return count;
    }
}

注意:原型Bean虽然线程安全,但有性能开销(频繁创建对象)。

3. request/session/application - 根据隔离级别决定

  • request作用域:每个HTTP请求独立,请求内线程安全
  • session作用域:每个会话独立,会话内线程安全
  • application作用域:全局共享,与singleton相同的线程安全问题

线程安全解决方案

方案1:无状态设计(推荐)

最佳实践是设计无状态的Bean,不使用可变成员变量。

// ✅ 线程安全的无状态Bean
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;  // 注入的依赖(不可变)
    
    // 没有可变成员变量,所有数据通过参数传递
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

方案2:使用ThreadLocal

每个线程维护自己的变量副本,实现线程隔离。

@Service
public class ThreadLocalService {
    
    private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();
    
    public void setCurrentUser(User user) {
        CURRENT_USER.set(user);
    }
    
    public User getCurrentUser() {
        return CURRENT_USER.get();
    }
    
    public void clear() {
        CURRENT_USER.remove();  // 务必清理,避免内存泄漏
    }
}

// 使用拦截器自动清理
@Component
public class UserContextInterceptor implements HandlerInterceptor {
    
    @Autowired
    private ThreadLocalService threadLocalService;
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        threadLocalService.clear();  // 请求结束后清理
    }
}

ThreadLocal最佳实践

  1. 使用static final修饰
  2. 使用完毕后调用remove()清理
  3. 避免在线程池场景下内存泄漏

方案3:使用同步机制

3.1 synchronized关键字

@Service
public class SynchronizedService {
    
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

优点:简单易用
缺点:性能较差,锁粒度大

3.2 Lock锁

@Service
public class LockService {
    
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

优点:更灵活(可中断、可超时)
缺点:代码复杂度高

3.3 原子类

@Service
public class AtomicService {
    
    private final AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
}

优点:性能好,无锁算法(CAS)
缺点:只适用于简单类型

方案4:不可变对象

@Service
public class ImmutableService {
    
    private final String serviceName;
    private final int maxRetry;
    
    // 所有字段都是final,构造后不可变
    public ImmutableService(@Value("${service.name}") String serviceName,
                           @Value("${service.maxRetry}") int maxRetry) {
        this.serviceName = serviceName;
        this.maxRetry = maxRetry;
    }
    
    // 只提供读取方法,不提供修改方法
    public String getServiceName() {
        return serviceName;
    }
    
    public int getMaxRetry() {
        return maxRetry;
    }
}

方案5:使用并发集合

@Service
public class CacheService {
    
    // 使用线程安全的集合
    private final ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
    private final CopyOnWriteArrayList<String> recentSearches = new CopyOnWriteArrayList<>();
    
    public void cacheUser(String id, User user) {
        userCache.put(id, user);
    }
    
    public User getUser(String id) {
        return userCache.get(id);
    }
    
    public void addSearch(String keyword) {
        recentSearches.add(keyword);
    }
}

实战案例

案例1:Controller中的线程安全

// ❌ 错误:Controller中使用可变成员变量
@RestController
@RequestMapping("/api/users")
public class UnsafeUserController {
    
    private User currentUser;  // 多个请求共享,线程不安全
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        currentUser = userService.findById(id);
        return currentUser;
    }
}

// ✅ 正确:不使用成员变量
@RestController
@RequestMapping("/api/users")
public class SafeUserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);  // 通过参数传递
    }
}

// ✅ 正确:使用ThreadLocal或request作用域
@RestController
@RequestMapping("/api/users")
public class SafeUserController2 {
    
    @Autowired
    private ThreadLocalService threadLocalService;
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        threadLocalService.setCurrentUser(user);
        return user;
    }
}

案例2:Service中的线程安全

// ❌ 错误:Service中缓存可变对象
@Service
public class UnsafeOrderService {
    
    private List<Order> cachedOrders = new ArrayList<>();  // 线程不安全
    
    public void addOrder(Order order) {
        cachedOrders.add(order);  // 并发修改异常
    }
}

// ✅ 正确:使用线程安全的集合
@Service
public class SafeOrderService {
    
    private final ConcurrentHashMap<Long, Order> orderCache = new ConcurrentHashMap<>();
    
    public void addOrder(Order order) {
        orderCache.put(order.getId(), order);
    }
    
    public Order getOrder(Long id) {
        return orderCache.get(id);
    }
}

// ✅ 更好:使用专业的缓存框架
@Service
public class BetterOrderService {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Cacheable(value = "orders", key = "#id")
    public Order getOrder(Long id) {
        return orderRepository.findById(id).orElse(null);
    }
}

案例3:DateFormat线程安全问题

// ❌ 错误:SimpleDateFormat不是线程安全的
@Service
public class UnsafeDateService {
    
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
    public String format(Date date) {
        return sdf.format(date);  // 线程不安全
    }
}

// ✅ 方案1:使用ThreadLocal
@Service
public class SafeDateService1 {
    
    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = 
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    
    public String format(Date date) {
        return DATE_FORMAT.get().format(date);
    }
}

// ✅ 方案2:使用DateTimeFormatter(推荐)
@Service
public class SafeDateService2 {
    
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd");  // 线程安全
    
    public String format(LocalDate date) {
        return date.format(FORMATTER);
    }
}

// ✅ 方案3:每次创建新实例
@Service
public class SafeDateService3 {
    
    public String format(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(date);
    }
}

常见陷阱与最佳实践

陷阱1:注入的Bean也是单例

@Service
public class ServiceA {
    
    @Autowired
    private ServiceB serviceB;  // serviceB也是单例
    
    // 如果ServiceB不是线程安全的,这里也会有问题
}

陷阱2:静态变量

@Service
public class StaticFieldService {
    
    private static int count = 0;  // 静态变量在所有实例间共享
    
    public void increment() {
        count++;  // 线程不安全
    }
}

最佳实践

  1. 优先设计无状态Bean
  2. 避免在单例Bean中使用可变成员变量
  3. 使用ThreadLocal时务必清理
  4. 对于必须有状态的Bean,使用prototype作用域
  5. 使用并发工具类(Atomic、ConcurrentHashMap等)
  6. 注意第三方库的线程安全性(如SimpleDateFormat)

性能对比

// 性能测试
@SpringBootTest
public class PerformanceTest {
    
    @Test
    public void comparePerformance() throws Exception {
        int iterations = 1000000;
        
        // 1. 无锁(最快)
        AtomicInteger atomic = new AtomicInteger(0);
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            atomic.incrementAndGet();
        }
        System.out.println("Atomic: " + (System.currentTimeMillis() - start1) + "ms");
        
        // 2. synchronized(较慢)
        int[] count = {0};
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            synchronized (count) {
                count[0]++;
            }
        }
        System.out.println("Synchronized: " + (System.currentTimeMillis() - start2) + "ms");
        
        // 3. ReentrantLock(较慢)
        ReentrantLock lock = new ReentrantLock();
        int[] count2 = {0};
        long start3 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            lock.lock();
            try {
                count2[0]++;
            } finally {
                lock.unlock();
            }
        }
        System.out.println("ReentrantLock: " + (System.currentTimeMillis() - start3) + "ms");
    }
}

性能排序:无锁 > Atomic > synchronized > Lock

面试总结

核心要点

  1. Spring Bean默认不是线程安全的,需要开发者自行保证
  2. 单例Bean是最常见的线程安全问题来源
  3. 无状态Bean天然线程安全,推荐设计
  4. 原型Bean每次创建新实例,避免共享状态
  5. 解决方案:无状态设计、ThreadLocal、同步机制、原子类、并发集合

常见面试问题

Q1: Spring的单例Bean在多线程环境下安全吗?
A: 不一定。如果是无状态Bean(没有可变成员变量),则线程安全;如果有可变状态,需要额外处理。

Q2: 如何保证单例Bean的线程安全?
A: ① 设计为无状态Bean;② 使用ThreadLocal;③ 使用同步机制;④ 使用并发工具类;⑤ 改为prototype作用域。

Q3: Controller是线程安全的吗?
A: Controller默认是单例,如果不使用成员变量则线程安全;如果使用了成员变量,则不安全。

Q4: SimpleDateFormat为什么不能作为成员变量?
A: SimpleDateFormat不是线程安全的,在单例Bean中使用会导致并发问题,应使用ThreadLocal或DateTimeFormatter。

Q5: prototype作用域能解决所有线程安全问题吗?
A: 不能。prototype只是每次创建新实例,但如果对象本身不是线程安全的(如使用了非线程安全的集合),仍然有问题。