您的位置:首页 > 其它

servlet线程安全性问题理解

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

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

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

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

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

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

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

1.不用实例变量、

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

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

用关键字synchronized

下面看个列子

public class PrintServlet extends HttpServlet {

String testStr;

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

String username;

response.setContentType("text/html; charset=gb2312");

testStr= request.getParameter("username");

try {

Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时

} catch (Exception e) {

}

System.out.println("testStr="+testStr);

}

}

两个线程
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

public class PrintServlet extends HttpServlet {

String testStr;

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

String username;

response.setContentType("text/html; charset=gb2312");

synchronized(this){

testStr= request.getParameter("username");

//这样当a进来的时候因为有了同步代码块,b进不去,但是也会影响效率,所以应该少使用,同步代码块的内容也应该最精简

}

try {

Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时

} catch (Exception e) {

}

System.out.println("testStr="+testStr);

}

}

再来看看,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();
}
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: