Servlet 并发加锁
2019-03-20 本文已影响0人
旦暮何枯
servlet 处理实现机制
-
客户端发送请求 -> servlet 容器(Tomcat) -> 调度器 -> 在容器的线程池中选择一个工作组线程 -> 执行 servlet A 的 service 方法
-
客户端发送第二条请求 -> servlet 容器(Tomcat)-> 调度器 -> 在容器的线程池中选择第二个工作组线程 -> 并发执行 servlet A 的 service 方法
当有线程执行完毕,返回线程池
当线程池中的所有线程都在执行服务,新来的请求排队
当超过了设置的最大排队数量的请求到来时,容器会拒绝处理新的请求
线程模型
线程模型.png总结特点
- 单实例
- 容器中的 servlet 只会初始化一次,所有针对该 servlet 的请求,都只有这一个实例实例对象
- 多线程
- 多个请求的处理是由多个线程完成的,可以同时进行处理
- 线程不安全
- 多线程共用一个单实例对象,因为没有默认加锁操作,所以会出现同时请求一个实例对象不同操作,造成数据不一致的情况。
线程安全
变量线程安全
- 参数变量本地化
- 尽量使用局部变量,多线不共享局部变量
- 使用同步块 synchronized
- 对代码进行加锁处理,尽量缩小代码范围,不要在 service 方法中增加关键字,否则对性能损耗比较大
属性的线程安全
- ServletContext 线程安全
- 多线程同时读写需要做数据同步的处理
- HttpSession 理论上线程安全
- 只能在处理同一个用户的 Session 时,被访问;用户打开多个属于同一个进程的的浏览器窗口时,这些窗口的访问属于同一个 session 会出现多次请求。需要多个工作线程的处理,可能也会和出现多个线程的读写属性的问题
- ServletRequest 线程安全
- 每一个请求只有一个工作线程处理,只能在同一个线程中被访问;所以安全
避免在 Servlet 中创建线程
servlet 本身就是在多线程中进行处理的,在 servlet 中再创建线程;会造成运行情况复杂,更容易发生多线程安全问题。
多个 Servlet 访问外部对象加锁
使用 synchronized 同步块进行加锁操作
- 创建一个 maven web-app
- 创建一个 SynchronizedTest 的 servlet 类
- 在 servlet 类中的 doGet 方法中使用 synchronized 标识进行加锁
- 在部署描述符中注册
SynchronizedServlet 类
public class SynchronizedServlet extends javax.servlet.http.HttpServlet {
String name; // 为引入线程不安全问题引入实例变量,不推荐使用实例变量
@Override
public void init() throws ServletException {
super.init();
}
@Override
public void destroy() {
super.destroy();
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//使用 synchronized 标识进行代码加锁
synchronized (this) {
name = request.getParameter("username");//从 url 中获取 username 的值
PrintWriter printWriter = response.getWriter(); // 创建 PrintWriter 对象,在页面中打印
try {
//为模拟大量并发 设置线程休眠时间。
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
printWriter.print("username: " + name);
}
}
}
『项目目录』:https://github.com/wengfe/JAVA/tree/master/SynchronizedTest/src/main