Servlet的多线程安全问题
public class MyServlet extends HttpServlet { final static int i = 0; public void doGet(HttpServletRequest req, HttpServletResponse res) { private HttpSession session = req.getSession(); private ServletContext ctx = getServletContext(); synchronized (ctx) { Object obj = ctx.getAttribute(); // code to alter obj } } }
上面代码中的哪些变量是线程安全的?
选择:
* A. i
* B. session
* C. ctx
* D. req
* E. obj
* F. res
IBM 给出的答案是:
正确答案:
* A、C、D 和 F
说明:
静态变量 i 是线程安全的,因为它是 final(不能被修改),否则它将不是安全的。请求和响应对象的作用域只在请求的生命周期,因此它们也是线程安全的。会话和 ServletContext 对象可以从多个线程访问,同时处理多个请求,因此它们不是线程安全的。但在本例中,同步了 ServletContext 对象,因此它只能由一个线程一次访问。obj 不是线程安全的,因为即使同步了 ServletContext 对象,它的属性也没有同步。它们需要另外进行同步。因此,选项 B 和 E 是不正确的,而选项 A、C、D 和 F 是正确的。
Servlets的多线程安全
多线程占用资源少,处理速度快,提高了效率。
一些编码建议:
对变量和方法定义适当的访问方式, 例如单纯取值操作不会有多线程安全问题;
同步化所有访问重要数据的实例变量; 多线程下,如果操作的是一个变量,且兼有读写操作,
就要考虑加上同步,但同步不能乱加,否则会造成死锁问题。
并发需要注意的
并发的环境:资源处于一个并发的环境
共享资源:多个线程共享一个临界资源
全面同步:如有n个变量访问同一个资源,这n个变量都得同步。即多个锁一把钥匙,钥匙放在一个共享区域内
sychronized(this):粗粒度的锁。是将所有的路都加锁;
sychronized(object o1):细粒度的锁。只对对象中的变量加锁。效率较前面的高,但是较难控制。
读写需要互斥。
sychronized(this):this不能是基本数据类型,必须是Object.不锁对象的引用,而是对象的内存空间。
servlet中需要同步的:成员变量、文件、静态变量、数据库连接
一,servlet容器如何同时处理多个请求。
Servlet采用多线程来处理多个请求同时访问,Servlet容器维护了一个线程池来服务请求。
线程池实际上是等待执行处理的一组线程,也叫做工作者线程(Worker Thread),
Servlet容器使用一个调度线程来管理工作者线程(Dispatcher Thread)。
当容器收到一个访问Servlet的请求,调度者线程从线程池中选出一个工作者线程,将请求传递给该线程,
然后由该线程来执行Servlet的service方法。
当这个线程正在执行的时候,容器收到另外一个请求,调度者线程将从池中选出另外一个工作者线程来服务新的请求;
容器并不关心这个请求是否访问的是同一个Servlet还是另外一个Servlet;
当容器同时收到对同一Servlet的多个请求,那这个Servlet的service方法将以多线程方式并发执行。
二,Servlet容器默认采用单实例多线程的方式来处理请求,减少产生Servlet实例的开销,提升了对请求的响应。
对于Tomcat可以在server.xml中通过元素设置线程池中线程的数目。
就实现来说:
调度者线程类所担负的责任是调度线程,只需要利用自己的属性完成自己的责任。
而其他对象又依赖于该对象所承担的责任,需要得到该特定对象,那该类就是一个单例模式的实现了。
三,如何开发线程安全的Servlet
1,变量的线程安全:这里的变量指字段和共享数据(如表单参数值)。
a,将参数变量本地化:多线程并不共享局部变量.所以我们要尽可能的在servlet中使用局部变量。
例如:String user = request.getParameter(“user”);
b,使用同步块Synchronized,防止可能异步调用的代码块。这意味着线程需要队列处理。
在使用同板块的时候要尽可能的缩小同步代码的范围,不要直接在sevice方法和响应方法上使用同步,这样会严重影响性能。
2,属性的线程安全分析:ServletContext,HttpSession,ServletRequest对象的属性
ServletContext:(线程是不安全的)
ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。
所以在Servlet上下文中尽可能少地保存频繁改写的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。
HttpSession:(线程是不安全的)
HttpSession对象在用户会话期存在,只能处理属于同一个Session的请求的线程,因此Session对象的属性访问理论上是线程安全的。
当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性,这时我们对属性的读写进行同步处理。
ServletRequest:(线程是安全的)
对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。
3,使用同步的集合类:
使用Vector代替ArrayList,使用Hashtable代替HashMap。
4,不要在Servlet中创建自己的线程来完成某个功能:
Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化,出现安全问题。
5,在多个servlet中,对外部对象(例如文件)进行修改操作一定要加锁,做到互斥的访问
四,SingleThreadModel接口
javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,
则Servlet容器将保证在同时刻仅有一个线程可以在该servlet实例的service方法中执行,将其他所有请求进行排队。
服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的性能问题。
服务器可创建一个Servlet类的多个实例组成的实例池,对于每个请求分配Servlet实例进行响应,之后放回到实例池中等待下此请求。此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。
而对于这种多实例的情况,使用SingleThreadModel接口并不能解决并发访问产生的问题,
且SingleThreadModel接口在servlet规范中已经被明确声明为deprecated了。