本篇供个人学习使用,有问题欢迎讨论
Servlet的线程安全问题
Servlet 是在单例多线程环境下运行的。其运行可能会出现线程安全问题
一、线程安全问题
1、什么是线程安全问题
同时满足以下两个条件,则会出现线程安全问题:
(1)存在多线程并发访问
(2)存在可修改的共享数据
当多个线程同时修改同一个共享数据时,后修改的数据会将先修改的数据覆盖,对数据先进行修改的用户读取到的不是自己修改后的数据,这就是线程安全问题。
2、JVM中可能存在线程安全问题的数据分析
(1)栈内存数据分析
栈内存是多例的,即 JVM 会为每个线程创建一个栈, 所以其中的数据不是共享的。另外,方法中的局部变量存放在 Stack 的栈帧中,方法执行完毕,栈帧弹栈,局部变量消失。局部变量是局部的,不是共享的。所以栈内存中的数据不存在线程安全问题。
(2)堆内存数据分析
一个 JVM 中只存在一个堆内存,堆内存是共享的。被创建出的对象是存放在堆内存的,而存放在堆内存中的对象,实际就是对象成员变量的值的集合。即成员变量是存放在堆内存的。堆内存中的数据是多线程共享的,也就是说,堆内存中的数据是存在线程安全问题的。
(3)方法区数据分析
一个JVM中只存在一个方法区。静态变量与常量存放在方法区,方法区是多线程共享的。常量是不能被修改的量,所以常量不存在线程安全问题。静态变量是多线程共享的,所以静态变量存在线程安全问题。
3、线程安全问题的解决方案
若要解决数据的线程安全问题,则可按照下面思路考虑:
(1)对于一般性的类,不要定义为单例的。除非项目有特殊需求,或该类对象属于重量级对象。所谓重量级对象是指,创建该类对象时需要占用较大的系统资源。
(2)无论类是否为单例类,尽量不使用静态变量。
(3)若需要定义为单例类,则单例类中尽量不使用成员变量。
(4)若单例类中必须要使用成员变量,则对成员变量的操作,可以添加串行化锁 synchronized,实现线程同步。不过,最好不要使用线程同步机制。因为一旦操作进入串行化的排队状态,将大大降低程序的执行效率。
二、Servlet 的线程安全问题
Servlet是单例多线程并发访问的,所以其就有可能会出现线程安全问题。为了避免线程、安全问题的产生,对于Servlet 的使用,一般是不声明成员变量的。 若项且中要求必须要声明成员变量,则只能通过线程同步机制 synchronized 避免。
1、程序演示
由于 Servlet 是单例多线程的,而其中又存在可修改的成员变量 username,所以,当前这个 OneServlet 存在线程安全问题,即这个 Servlet 是线程不安全的。
private String username; //成员变量
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
username = request.getParameter("username"); //对成员变量的修改
PrintWriter out = response.getWriter();
out.println("username = " + username);
}
打开两个网页,对程序添加断点,如在以下网页提交用户名 lisi :
此时,程序运行到断点处,再打开另一个网页,输入用户名为 zhangsan:
此时程序依然停在断点处,对断点执行,进行到下一步,即在网页显示用户名,这时会发现提交的用户名是 zhangsan,但页面显示的却是 lisi。这就是线程安全问题!
2、线程安全问题的两种解决方案
(1)声明为局部变量
String username = request.getParameter("username");
(2)对程序添加串行化锁 synchronized(效率低)
synchronized (this){
username = request.getParameter("username");
PrintWriter out = response.getWriter();
out.println("username = " + username);
}
三、对线程安全问题的合理利用
Servlet 中的成员变量是每一个线程均可修改和访问的,所以可以利用这一点,实现计数器功能,用于统计当前 Servlet 的被访问的次数。
private int count; //成员变量
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
count++; //对当前访问的Servlet进行计数,每访问一次自增1
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("当前页面已被浏览过" + count + "次。");
}
直接访问 OneServlet,以下页面表示访问的次数是第一次:
点击刷新,或是关掉窗口重新访问,依然会被 OneServlet 进行计数!