From a903dacb214325cc4da5e1d6f48500b938c758e2 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Wed, 28 Oct 2026 03:18:55 +0800 Subject: [PATCH] Update Spring.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充 Bean 生命周期的完整输出,增加单例 Bean 并发计数的实测附录。 --- notes/Spring.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/notes/Spring.md b/notes/Spring.md index fb75c678..ad070e1f 100644 --- a/notes/Spring.md +++ b/notes/Spring.md @@ -22,6 +22,7 @@ * [动态 SQL](#动态-sql) * [缓存](#缓存) * [七、Spring 中的设计模式](#七spring-中的设计模式) + * [附录:实测](#附录实测) * [参考资料](#参考资料) @@ -77,7 +78,7 @@ public class OrderService { 6. BeanPostProcessor 的 postProcessAfterInitialization(),AOP 就是在这一步把 Bean 替换为代理对象的; 7. 容器关闭时销毁:依次调用 @PreDestroy 方法、DisposableBean 的 destroy() 和配置的 destroy-method。 -让一个 Bean 实现上面所有的回调,实际运行打印的顺序如下: +让一个 Bean 实现上面所有的回调,实际运行打印的顺序如下(Spring Boot 3.3): ```text 1 构造函数 @@ -90,6 +91,7 @@ public class OrderService { 8 BeanPostProcessor after 9 @PreDestroy 10 DisposableBean.destroy +11 destroy-method ``` ### Bean 的作用域与线程安全 @@ -111,7 +113,7 @@ public class CounterController { } ``` -在本机用 64 个线程并发请求 1 万次,计数只增加了 9975,丢失了 25 次更新。解决方法是不在单例中保存状态,把数据放在方法的局部变量中;确实需要共享时使用 AtomicInteger 等线程安全的类,每个线程各自的数据放在 ThreadLocal 中。 +例如用 64 个线程并发请求 1 万次,多次运行中每轮丢失的更新从 0 次到 67 次不等(Spring Boot 3.3 实测,见[附录:实测](#附录实测))。是否丢失、丢失多少取决于线程执行的时序,这类问题在测试中很难稳定复现。解决方法是不在单例中保存状态,把数据放在方法的局部变量中;确实需要共享时使用 AtomicInteger 等线程安全的类,每个线程各自的数据放在 ThreadLocal 中。 需要注意的是,把 prototype 的 Bean 注入到单例中,只会在创建单例时注入一次,之后每次使用的都是同一个对象。需要每次得到新对象时,注入 ObjectProvider 并在使用时调用 getObject()。 @@ -349,6 +351,29 @@ public void on(OrderPaidEvent e) { ... } // 积分服务、短信 两个监听方法都在 main 线程中执行;短信服务先执行并抛出异常后,积分服务的监听方法没有被调用,异常还传回了订单服务。监听方法之间的顺序默认不确定,可以用 @Order 指定;不希望互相影响时,加上 @Async 让监听方法在线程池中异步执行,或者用 @TransactionalEventListener 在订单的事务提交之后再处理。 +## 附录:实测 + +Spring Boot 3.3.5、JDK 21,Apple M1 Pro。CounterController 与正文相同,用 Python 的线程池以 64 个线程请求 /count,每轮 1 万次,用请求前后 /count 的返回值计算增加了多少,应用启动后连续运行 3 轮: + +```text +第 1 轮:64 个线程并发请求 10000 次,计数增加 9987,丢失 13 次更新 +第 2 轮:64 个线程并发请求 10000 次,计数增加 10000,丢失 0 次更新 +第 3 轮:64 个线程并发请求 10000 次,计数增加 10000,丢失 0 次更新 +``` + +2026-10-28 重新启动应用后再运行两组: + +```text +== 启动后第 1 组 +第 1 轮:64 个线程并发请求 10000 次,计数增加 9933,丢失 67 次更新 +第 2 轮:64 个线程并发请求 10000 次,计数增加 9986,丢失 14 次更新 +第 3 轮:64 个线程并发请求 10000 次,计数增加 9997,丢失 3 次更新 +== 启动后第 2 组 +第 1 轮:64 个线程并发请求 10000 次,计数增加 9999,丢失 1 次更新 +第 2 轮:64 个线程并发请求 10000 次,计数增加 9997,丢失 3 次更新 +第 3 轮:64 个线程并发请求 10000 次,计数增加 9998,丢失 2 次更新 +``` + ## 参考资料 - [Spring Framework: Dependency Injection](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)