`
Before_Morning
  • 浏览: 34966 次
文章分类
社区版块
存档分类
最新评论

温故知新--Servlet(四)--servlet线程安全问题

 
阅读更多

对于servlet的线程安全问题为何会出现呢?从前面的servlet的生命周期的学习我们知道,由于通常情况下,一个Servlet在内存只有一个实例处理请求,当多个请求发送过来的时候就会有多个线程操作该servlet对象,此时可能导致线程安全问题。

下面我们首先通过两个例子来进行一下对比来引出线程安全问题的讨论。

首先,看Demo1,为了更好的说明问题,测试的时候我用两个浏览器去访问同一个Servlet(模拟多个用户访问的case)

package com.jjyy.servlet.thread;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * servlet线程安全问题分析  --Demo1
 * @author JiangYu
 *
 */
public class ServletThreadSaveAnalysis extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		int i = 0;
		response.setContentType("text/html;charset=utf-8");
		response.getWriter().write("测试结果:"+(++i));
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

	}

}


结果如下:

先不做解释,我们在看第二个例子:代码如下

package com.jjyy.servlet.thread;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * servlet线程安全问题分析  --Demo2
 * @author JiangYu
 *
 */
public class ServletThreadSaveAnalysis extends HttpServlet {

	int i = 0;
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		response.getWriter().write("测试结果:"+(++i));
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

	}

}


同样,我开启两个浏览器同时去访问,结果如下:

我再次刷新右边的浏览器,结果如下:

怎么回事,一下子就从12变成了23。到此,问题就出现了。那么为何会出现这样的情况呢?我们可以对比上面的两段代码,唯一不同之处就是,第一个中的i是局部变量,二个程序中的i是一个全局变量,所以应该知道问题的原因了吧!

由于通常情况下,一个Servlet在内存只有一个实例处理请求,当多个请求发送过来的时候就会有多个线程操作该servlet对象,这里我们模拟的是两个用户来回的刷新浏览器,当servlet中存在全局变量的时候,自然每次请求都会对其进行改变,而对于局部变量则不会存在这样的问题。

既然存在问题,我们猜肯定有解决的方法:下面提供几种供参考的解决方法

(1)利用同步代码块解决问题。

package com.jjyy.servlet.thread;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * servlet线程安全问题分析  --Demo3
 * @author JiangYu
 *
 */
public class ServletThreadSaveAnalysis extends HttpServlet {

	int i = 0;
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//加锁的方式
		synchronized (this) {
			try {
				//模拟延迟
				Thread.sleep(4000);
				response.setContentType("text/html;charset=utf-8");
				response.getWriter().write("测试结果:"+(++i));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

	}

}

虽然加锁可以防止问题的发生,但是如果是高并发量访问的情况下,每一个请求都要持续四秒的时间的话,第二个就要等8秒,第三个就要等12秒,……,我们现在是模拟的4秒延迟,但是实际情况中,如果一个人来访问就加上锁的话,如果他一直不释放锁,那么可想而知这是行不通的。所以说加锁方式缺陷是,同一时间同步代码块只能处理一个请求,效率很低下,所以同步代码块中尽量只包含核心的导致线程安全问题的代码。

(2)为该servlet实现SingleThreadModel接口

package com.jjyy.servlet.thread;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * servlet线程安全问题分析  --Demo4
 * @author JiangYu
 *
 */
public class ServletThreadSaveAnalysis extends HttpServlet implements SingleThreadModel {

	int i = 0;
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		response.getWriter().write("测试结果:"+(++i));
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

	}

}

此为一个标记接口,被标记的servlet将会在内存中保存一个servlet池,如果一个线程来了而池中没有servlet对象处理,则创建一个新的。如果池中有空闲的servlet则直接使用。为什么会存在问题呢?首先,还是上面的代码,假设我们有两个浏览器同时第一次来请求这个Servlet,这两个线程都到servlet池中看一下,发现没有servlet对象,就都各自创建了一个servlet对象,当使用完之后都放回池中,此时如果有第三个线程来访问,它随便取哪一个都是输出2,它使用完后,将servlet放入池中,此时有第4个线程来访问,第一次可能输出3,第二次可能输出2,为何,因为它第二次访问的时候可能拿到那个值为1的servlet对象,这是就会出现问题了。所以,SingleThreadModel接口并不能真的解决线程安全问题。并且此接口已经被废弃。

由于默认情况下Servlet在内存中只有一个对象,当多个浏览器并发访问Servlet时就有可能产生线程安全问题,综上分析,

1.加锁--效率降低

2.SingleThreadModel接口--不能真的防止线程安全问题

最终解决方案:

在Servlet中尽量少用类变量,如果一定要用类变量则用锁来防止线程安全问题,但是要注意锁住内容应该是造成线程安全问题的核心代码,尽量的少锁主内容,减少等待时间提高servlet的响应速度。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics