Servlet的线程安全问题

为了有效利用JVM允许多个线程访问同一个实例的特性,来提高服务器性能。在非分布式系统中,Servlet容器只会维护一个Servlet的实例。

如果 Web 应用中的 Servlet 被标注为分布式的,容器应该为每一个分布式应用程序的 JVM 维护一个 Servlet 实例池。

Servlet容器通过维护一个线程池来处理多个请求,线程池中维护的是一组工作者线程(Worker Thread)。Servlet容器通过一个调度线程(Dispatcher Thread)来调度线程池中的线程。

当客户端的servlet请求到来时,调度线程会从线程池中选出一个工作者线程并将请求传递给该线程,该线程就会执行对应servlet实例的service方法。同样,当客户端发起另一个servlet请求时,调度线程会从线程池中选出另一个线程去执行servlet实例的service方法。Servlet容器并不关心这些线程访问的是同一个servlet还是不同的servlet,当多个线程访问同一个servlet时,该servlet实例的service方法将在多个线性中并发执行。

所以,==Servlet对象是单实例多线程,Servlet不是线程安全的==

为什么不安全?

先看两个定义:
实例变量:实例变量在类中定义。类的每一个实例都拥有自己的实例变量,如果多个线程同时访问该实例的方法,而该方法又使用到实例变量,那么这些线程同时访问的是同一个实例变量,会共享该实例变量。

局部变量:局部变量在方法中定义。每当一个线程访问局部变量所在的方法时,在线程的堆栈中就会创建这个局部变量,线程执行完这个方法时,该局部变量就被销毁。所有多个线程同时访问该方法时,每个线程都有自己的局部变量,不会共享。

看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyServlet extends HttpServlet{
private static final long serialVersionUID = 1L;

private String userName1 = null;//实例变量

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
userName1 = req.getParameter("userName1");

String userName2 = req.getParameter("userName2");//局部变量

//TODO 其他处理
}
}

userName1则是共享变量,多个线程会同时访问该变量,是线程不安全的。

userName2是局部变量,不管多少个线程同时访问,都是线程安全的。

解决Servlet的线程安全问题

如果不涉及到全局共享变量,就直接使用局部变量

如果使用到全局共享的场景,可以使用加锁的方式.对全局变量的读写操作置于synchronized同步块中,这样不同线程排队依次执行该代码块,从而避免线程不安全情况发生。还可以使用线程安全的数据类型。比如hashtable,blockQueue等

参考

赏个包子钱~~