Servlet的线程安全问题
引入
首先看看这样的代码,有什么问题
这里既要求cmd不能包含Calculator
又必须要包含Calculator
,能做到吗,当然是可以的
Servlet的多线程机制
Servlet实际上是一个单件,当我们第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件或者是注解实例化这个Servlet类,之后如果又有新的客户端请求该Servlet时,则一般不会再实例化该Servlet类,这说明了什么呢?简单来说,当多个用户一起访问时,得到的其实是同一个Servlet实例,这样的话,他们对实例的成员变量的修改其实会影响到别人,所以在开发的时候如果没有注意这个问题往往会有一些额安全问题,而往往Servlet的线程安全问题主要是由于实例变量使用不当而引起
因此我们再看上面的代码,很明显我们看到了这个status
状态变量是实例变量,当然这里为了突出并发的效果,这里加了一个延时,这里简简单单用python实现竞争,也不必上多线程了简单点
url = "http://127.0.0.1:8080/?cmd=open -na Calculator" while 1: r = requests.get(url) if "Cal" in r.text: print(r.text)
url = "http://127.0.0.1:8080/?cmd=ls" while 1: r = requests.get(url)
如何修复
1.实现 SingleThreadModel 接口
该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法只要继承这个接口就行了,因此将我们上面的代码改为
public class TestServlet extends HttpServlet implements SingleThreadModel
这样你觉得就完全安全了吗??答案也不是,如果我们将上面的对状态的定义加上static呢
public static boolean status;
lol,还是可以成功,原因是SingleThreadModel不会解决所有的线程安全隐患。会话属性和静态变量仍然可以被多线程的多请求同时访问
还有一点很重要该接口在Servlet API 2.4中将不推荐使用。
2.避免使用成员变量
既然问题出自成员变量,那么我们就尽量避免去使用它
将上面的代码改为
public class TestServlet extends HttpServlet{ // public boolean status; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { boolean status = true; String cmd = req.getParameter("cmd"); if (cmd.contains("Calculator")) { status = false; try { Thread.sleep(1000); }catch (Exception e){ } } if (!status) { return; } if (cmd.contains("Calculator")){ resp.getWriter().write(cmd); } } }
3.同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,因此可以将代码写为
public class TestServlet extends HttpServlet{ public boolean status; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cmd = req.getParameter("cmd"); boolean status; synchronized(this) { status = true; if (cmd.contains("Calculator")) { status = false; try { Thread.sleep(5000); } catch (Exception e) { } } } if (!status) { return; } if (cmd.contains("Calculator")){ resp.getWriter().write(cmd); } } }
思考与小结
但是如果利用上面三种方式去修复,这样就完全没问题了吗?并不是
比如实现SingleThreadModel以及在程序中使用同步来保护要使用的共享的数据,在实际业务当中这也会使得我们系统的性能大大下降,这也是我们不太希望看到的,前者为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销,而后者被同步的代码块在同一时刻也只能有一个线程执行它,这也会导致在高并发的情况下,同时处理请求的吞吐量显著的降低
因此,在Serlet中避免使用实例变量或许是更好的选择,但如果无法避免,但如果无法避免,也应该尽量做到去同步可用性最小的代码路径
参考文章
https://www.cnblogs.com/chanshuyi/p/5052426.html
https://zhuanlan.zhihu.com/p/93708538
https://www.jianshu.com/p/06260e0667a9
到此这篇关于Servlet的线程安全问题 的文章就介绍到这了,更多相关Servlet 线程安全 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Spring Security OAuth2 实现登录互踢的示例代码
这篇文章主要介绍了Spring Security OAuth2实现登录互踢的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-04-04idea配置tomcat,idea配置web下lib的包详解
这篇文章主要介绍了idea配置tomcat,idea配置web下lib的包,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-05-05java:程序包javafx.geometry不存在问题及解决
这篇文章主要介绍了java:程序包javafx.geometry不存在问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-08-08MyEclipse打开文件跳转到notepad打开问题及解决方案
windows系统打开README.md文件,每次都需要右键选择notepad打开,感觉很麻烦,然后就把README.md文件打开方式默认选择了notepad,这样每次双击就能打开,感觉很方便,这篇文章主要介绍了MyEclipse打开文件跳转到notepad打开问题,需要的朋友可以参考下2024-03-03
最新评论