1. 避免共享状态(最佳实践)
-
核心思想:Servlet 本身应设计为无状态(Stateless),不依赖实例变量存储请求相关数据。
-
实现方式:
-
将变量声明在方法内部(局部变量),每个线程独享栈内存。
-
若需跨请求传递数据,使用请求作用域(
HttpServletRequest
)或会话作用域(HttpSession
)。
-
-
示例:
public class SafeServlet extends HttpServlet {// ❌ 危险:实例变量被所有线程共享// private int counter;protected void doGet(HttpServletRequest req, HttpServletResponse resp) {// ✅ 安全:局部变量,线程独享int localCounter = 0;localCounter++;resp.getWriter().write("Count: " + localCounter);} }
2. 使用线程安全的数据结构
-
适用场景:必须共享资源时(如全局计数器、缓存)。
-
实现方式:
-
使用
java.util.concurrent
包中的线程安全类:
ConcurrentHashMap
,AtomicInteger
,CopyOnWriteArrayList
等。 -
避免直接使用非线程安全的类(如
HashMap
、ArrayList
)。
-
-
示例:
public class CounterServlet extends HttpServlet {// ✅ 线程安全计数器private AtomicInteger atomicCounter = new AtomicInteger(0);protected void doGet(HttpServletRequest req, HttpServletResponse resp) {int count = atomicCounter.incrementAndGet();resp.getWriter().write("Atomic Count: " + count);} }
3. 同步(Synchronization)
-
适用场景:需保护临界区(Critical Section)代码时。
-
实现方式:
-
使用
synchronized
关键字修饰方法或代码块。 -
注意锁的粒度:尽量缩小同步范围以提高性能。
-
-
示例:
public class SyncServlet extends HttpServlet {private int counter = 0;private final Object lock = new Object(); // 专用锁对象protected void doGet(HttpServletRequest req, HttpServletResponse resp) {synchronized (lock) { // ✅ 同步代码块counter++;resp.getWriter().write("Sync Count: " + counter);}} }
4. 使用 ThreadLocal
-
适用场景:需要为每个线程维护独立副本的资源(如数据库连接、SimpleDateFormat)。
-
原理:通过
ThreadLocal
为每个线程创建资源副本,避免竞争。 -
示例:
public class DateFormatServlet extends HttpServlet {// ✅ 每个线程独立持有 SimpleDateFormatprivate static final ThreadLocal<SimpleDateFormat> dateFormat =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));protected void doGet(HttpServletRequest req, HttpServletResponse resp) {SimpleDateFormat sdf = dateFormat.get(); // 获取当前线程的副本String date = sdf.format(new Date());resp.getWriter().write(date);}@Overridepublic void destroy() {dateFormat.remove(); // 清理线程副本} }
5. 外部化资源管理
-
适用场景:数据库连接池、缓存等需线程安全的外部资源。
-
实现方式:
-
使用成熟的线程安全中间件(如 Redis、数据库连接池 HikariCP)。
-
确保资源本身是线程安全的(如 JDBC 的
DataSource
)。
-
6. Servlet 作用域控制(谨慎使用)
-
通过配置使 Servlet 非单例(仅特定容器支持,如通过
@WebServlet(urlPatterns="...", loadOnStartup=1, asyncSupported=true)
配置异步模式)。 -
替代方案:使用框架(如 Spring MVC 的
@Scope("prototype")
),但需权衡性能。
关键原则总结
策略 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
无状态设计 | 绝大多数情况 | 简单高效,无需同步 | 不适合必须共享资源的场景 |
线程安全类 | 共享计数器、缓存等 | 性能高,无需手动同步 | 功能受限 |
同步(synchronized) | 临界区操作(如文件写入) | 灵活,可控粒度 | 性能下降,可能死锁 |
ThreadLocal | 线程绑定资源(如数据库连接) | 避免竞争,资源隔离 | 内存泄漏风险 |
常见陷阱与解决方案
-
SimpleDateFormat 非线程安全
-
❌ 错误做法:
private SimpleDateFormat sdf = new SimpleDateFormat(...);
-
✅ 正确做法:使用
ThreadLocal
或替换为DateTimeFormatter
(Java 8+ 线程安全)。
-
-
Servlet 中存储用户状态
-
❌ 错误做法:在 Servlet 实例变量中保存用户数据。
-
✅ 正确做法:使用
HttpSession
或请求参数。
-
-
过度同步导致性能瓶颈
-
❌ 错误做法:
synchronized
修饰整个service()
方法。 -
✅ 正确做法:缩小同步范围至必要代码块。
-
通过合理选择上述策略,可以在保证线程安全的前提下,最大限度提升 Servlet 的并发性能