servlet线程安全性问题理解

时间:2021-07-19 13:31:41

Servlet的多线程机制

  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。

servlet 作为一个java的一个实例,

当多用户访问的时候,并不会重新创建实列,而是用同一个对象,而这样就会造成

线程的安全性问题。 我们知道实例变量是和实例的生命周期一致的,所以如果

servlet中不可避免要用到实例变量,那就要注意了。如果实例变量的值自始至终都不会改变

那就没问题,如果一旦有改变,就需要考虑线程安全了。

所以,java中servlet线程安全解决办法三:

1.不用实例变量、

2.servlet 实现SingleThreadModel 接口,但是很影响效率,强烈建议不使用

3.servlet对实例变量进行同步代码块进行处理

  用关键字synchronized


下面看个列子

  1. public class PrintServlet extends HttpServlet {  
  2.     String  testStr;  
  3.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  4.             throws ServletException, IOException {  
  5.         String username;  
  6.         response.setContentType("text/html; charset=gb2312");  
  7.         testStr= request.getParameter("username");  
  8.        
  9.         try {  
  10.             Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时  
  11.         } catch (Exception e) {  
  12.         }  
  13.         System.out.println("testStr="+testStr);
  14.     }  
  15. }  

两个线程 a b(假设a先进)

a--> 假设服务端传来了“张三”-->testStr="张三"休眠了-->输出:testStr="李四

b-->假设服务端传来了“李四”-->被覆盖为testStr="李四" 休眠-->输出:testStr="李四

上面的结果与我们预期的结果肯定不一致的。 所以解决办法 1.不使用全局变量;   把String teststr;定义在doPost()内部 2.让这个PrintServlet 实现接口SingleThreadMode public class PrintServlet extends HttpServlet implements  SingleThreadMode{ .............//这样客户端每次请求的时候,会重新实例化一个PrintServlet的对象,所以里面的全局变量就不是同一个了,但是非常影响服务器的响应速率,所以不要使用 } 3.servlet对实例变量进行同步代码块进行处理

  用关键字synchronized

  1. public class PrintServlet extends HttpServlet {  
  2.     String  testStr;  
  3.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  4.             throws ServletException, IOException {  
  5.         String username;  
  6.         response.setContentType("text/html; charset=gb2312");
  7.   synchronized(this){
  8.         testStr= request.getParameter("username");  
  9. //这样当a进来的时候因为有了同步代码块,b进不去,但是也会影响效率,所以应该少使用,同步代码块的内容也应该最精简
  10. }
  11.        
  12.         try {  
  13.             Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时  
  14.         } catch (Exception e) {  
  15.         }  
  16.         System.out.println("testStr="+testStr);
  17.     }  
  18. }  
再来看看,javaweb里面的三层架构MVC模式 public class EmployeeServlet extends HttpServlet {
private static final long serialVersionUID = 0;
IEmployeeService employeeService=new EmployeeServiceImpl();
IDepartmentService departmentService=new DepartmentServiceImpl();

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String m=request.getParameter("m");
if("list".equals(m))list(request,response);
if("del".equals(m))del(request,response);
if("add".equals(m))add(request,response);
if("prepare".equals(m))prepare(request,response);
if("prepareDs".equals(m))prepareDs(request,response);
if("modify".equals(m))modify(request,response);
if("batchDel".equals(m))batchDel(request,response);
}
/** * 添加员工 * @param request * @param response * @throws ServletException * @throws IOException */public void add(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {String name=request.getParameter("name");String sex=request.getParameter("sex");String age=request.getParameter("age");String idCard=request.getParameter("idCard");String idCardAddress=request.getParameter("idCardAddress");String joinDate=request.getParameter("joinDate");String nowAddress=request.getParameter("nowAddress");String departmentId=request.getParameter("departmentId");String headImg=request.getParameter("headImg");Employee employee=new Employee();employee.setName(name);try{employee.setAge(Integer.valueOf(age));employee.setDepartmentId(Integer.valueOf(departmentId));}catch(Exception e){//如何处理验证数据正确的处理}employee.setSex(sex.charAt(0));employee.setIdCard(idCard);employee.setIdCardAddress(idCardAddress);employee.setJoinDate(joinDate);employee.setNowAddress(nowAddress);employee.setHeadImg(headImg);UploadServlet.deFile=null;boolean result=false;//给页面结果提示if(employeeService.addEmployee(employee))result=true;request.setAttribute("result", result);prepareDs(request, response);}

在上面的代码中我们用到了,employeeService  这个是service层中的内容,它是一个全局变量,那根据我们刚才讲到的东西,是不是应该注意一下他的问题。这里我们可以看看他调用的方法。employeeService.addEmployee(employee)                    
我们用的是同一个对象,所以调用的方法,就是同一个方法。这个我直接进入我的方法代码段给分析下
public int insertEmployee(Employee e) {String sql="insert into employee values(null,?,?,?,?,?,?,?,?,?)"; try { PreparedStatement ps=dbUtil.getPrepareStatement(sql); ps.setString(1, e.getName()); ps.setString(2, String.valueOf(e.getSex())); ps.setInt(3, e.getAge()); ps.setString(4, e.getIdCard()); ps.setString(5, e.getJoinDate()); ps.setString(6, e.getIdCardAddress()); ps.setString(7, e.getNowAddress()); ps.setString(8, e.getHeadImg()); ps.setInt(9, e.getDepartmentId()); return dbUtil.executeUpdate(ps);} catch (SQLException e1) {e1.printStackTrace();return 0;//异常怎么处理}finally{dbUtil.closePs();dbUtil.closeConn();}}这里客户端传进来的  e是不同的对象,假设a中e的地址是11111, b中e的地址是00000,我发现,整句话的核心是在执行Ps这个预处理对象,那么他是不是同一个对象,呢。因为我用的dbUtil是一个单列对象,由工具类产生的我这里复制下。public class DBUtil {Connection conn = null;// 连接对象PreparedStatement ps = null;// 预编译语句对象ResultSet rs = null;// 结果集CallableStatement cs = null;// 调用存储过程的语句对象static final String proxoolDriver="org.logicalcobwebs.proxool.ProxoolDriver";//用的线程池static final String proxoolXml="proxool.myProxool";static final DBUtil instance= new DBUtil();/*** 单例模式* 用线程同步来保护getInstance方法* 保证在一个应用中只有一个该类的实例存在*///私有化构造方法,禁止外界对本类实例化操作,辅助实现单列private DBUtil() {}//单列模式,饿汉式创建public synchronized static DBUtil getInstance(){return instance;}/*懒汉式创建* public static DBUtil getInstance(){*if(instance == null){*synchronized(DBUtil.class){*if(instance == null)*instance = new DBUtil();*}*}* }* *//*** 获取连接对象* @return*/public Connection getConnection() {try {Class.forName(proxoolDriver);conn = DriverManager.getConnection(proxoolXml);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}return conn;}
/*** 获取预编译语句对象* @param sql带参数的sql语句* @return 预编译语句对象*/public PreparedStatement getPrepareStatement(String sql) {try {ps = getConnection().prepareStatement(sql);//这句话中前半句用的是固定的连接对象   coon,后半句是根据传入的sql产生的一个对象,他产生的ps是不同的对象。//所以,当insertEmployee(Employee e)执行的时候,产生的是不同的ps,返回的自然也是不同的处理结果了} catch (SQLException e) {e.printStackTrace();}return ps;}
/*** 执行预编译语句对象的查询操作* @param ps执行带参数语句* @return 结果集*/public ResultSet executeQuery(PreparedStatement ps) {try {rs = ps.executeQuery();} catch (SQLException e) {e.printStackTrace();}return rs;}
/*** 执行预编译语句对象的增删改操作* @param ps* @return 影响行数*/public int executeUpdate(PreparedStatement ps) {int count = 0;try {count = ps.executeUpdate();} catch (SQLException e) {e.printStackTrace();}return count;}
/*** 调用执行存储过程并传参* @param proc* @param os* @return 结果集*/public ResultSet getRSWithProc(String proc, Object[] os) {try {cs = getConnection().prepareCall(proc);if (os != null) {for (int i = 0; i < os.length; i++) {if (os[i] instanceof String)cs.setString(i + 1, (String) os[i]);if (os[i] instanceof Integer)cs.setInt(i + 1, (Integer) os[i]);}}rs = cs.executeQuery();} catch (SQLException e) {e.printStackTrace();}return rs;}
/*** 关闭三个对象的方法,注意顺序*//*** 关闭结果集*/public void closeRs() {try {if (rs != null) {rs.close();rs = null;}} catch (SQLException e) {e.printStackTrace();}}
/*** 关闭预编译语句对象*/public void closePs() {try {if (ps != null) {ps.close();ps = null;}} catch (SQLException e) {e.printStackTrace();}}
/*** 关闭调用存储过程的语句对象*/public void closeCs() {try {if (cs != null) {cs.close();cs = null;}} catch (SQLException e) {e.printStackTrace();}}
/*** 关闭连接对象*/public void closeConn() {try {if (conn != null) {conn.close();conn = null;}} catch (SQLException e) {e.printStackTrace();}}}