servlet线程安全性问题理解

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;// 调用存储过程的语句对象
s tatic 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();
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值