最近看到一个问题,即所有200个Web容器线程都挂起,这意味着没有一个可用于服务传入请求,因此应用程序冻结。
这是一个简单的Web应用程序和JMeter测试,我认为这可以说明这个问题的原因。 Web应用程序由两个类组成,即以下servlet:
public class SessionTestServlet extends HttpServlet {
protected static final String SESSION_KEY = "session_key";
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// set data on session so the listener is invoked
String sessionData = new String("Session data");
request.getSession().setAttribute(SESSION_KEY, sessionData);
PrintWriter writer = response.getWriter();
writer.println("<html><body>OK</body></html>");
writer.flush();
writer.close();
}
}
以及HttpSessionListener和HTTPSessionAttributeListener的以下实现:
public class SessionTestListener implements
HttpSessionListener, HttpSessionAttributeListener {
private static final ConcurrentMap<String, HttpSession> allSessions
= new ConcurrentHashMap<String, HttpSession>();
public void attributeRemoved(HttpSessionBindingEvent hsbe) {}
public void attributeAdded(HttpSessionBindingEvent hsbe) {
System.out.println("Attribute added, " + hsbe.getName()
+ "=" + hsbe.getValue());
int count = 0;
for (HttpSession session : allSessions.values()) {
if (session.getAttribute(SessionTestServlet.SESSION_KEY) != null) {
count++;
}
}
System.out.println(count + " of " + allSessions.size()
+ " sessions have attribute set.");
}
public void attributeReplaced(HttpSessionBindingEvent hsbe) {}
public void sessionCreated(HttpSessionEvent hse) {
allSessions.put(hse.getSession().getId(), session);
}
public void sessionDestroyed(HttpSessionEvent hse) {
allSessions.remove(hse.getSession().getId());
}
}
JMeter测试每秒都有100个请求命中servlet:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.1">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<longProp name="ThreadGroup.start_time">1327193422000</longProp>
<longProp name="ThreadGroup.end_time">1327193422000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">9080</stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/SESSION_TESTWeb/SessionTestServlet</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
<stringProp name="ConstantTimer.delay">1000</stringProp>
</ConstantTimer>
<hashTree/>
</hashTree>
</hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</jmeterTestPlan>
当针对部署在WebSphere 7上的测试Web应用程序运行此测试时,应用程序会快速停止响应,并且核心转储显示:
1LKDEADLOCK Deadlock detected !!!
NULL ---------------------
NULL
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600)
3LKDEADLOCKWTR is waiting for:
4LKDEADLOCKMON sys_mon_t:0x00000000151938C0 infl_mon_t: 0x0000000015193930:
4LKDEADLOCKOBJ com/ibm/ws/session/store/memory/MemorySession@00000000A38EA0C8/00000000A38EA0D4:
3LKDEADLOCKOWN which is owned by:
2LKDEADLOCKTHR Thread "WebContainer : 1" (0x00000000021FB500)
3LKDEADLOCKWTR which is waiting for:
4LKDEADLOCKMON sys_mon_t:0x0000000015193820 infl_mon_t: 0x0000000015193890:
4LKDEADLOCKOBJ com/ibm/ws/session/store/memory/MemorySession@00000000A14E22C0/00000000A14E22CC:
3LKDEADLOCKOWN which is owned by:
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600)
NULL
当执行servlet的doGet()方法的线程(T1)在HttpSession实现(S1)的实例上调用setAttribute()时,它会锁定在S1的监视器上。在持有该锁的同时,它进入侦听器的attributeAdded()方法内的allSessions的迭代,并调用getAttribute()。它看起来像在getAttribute()里面,WebSphere锁定了该实例的监视器(可能是因为它设置了lastUpdateTime字段?)。因此,T1将依次锁定S1,S2,S3,S4,S5的监视器......同时在servlet中的setAttribute()调用中保持对S1的锁定。
因此,如果同时另一个线程(T2)锁定在servlet中的另一个会话(S2)的监视器上,然后进入addAttribute()中的循环,那么S1和S2监视器上的线程将死锁。 / p>
我一直无法在J2EE规范中找到任何明确的内容,但Servlet 2.4规范的这一部分暗示容器不应该在HttpSession实现的实例上进行同步:
SRV.7.7.1线程问题
执行请求线程的多个servlet可能具有活动访问权限 一个会话对象同时。开发者有 负责同步对会话资源的访问 合适的。
当我们对它运行测试时,JBoss没有显示任何死锁。所以我的问题是:
由于
答案 0 :(得分:3)
Servlet 2.5 MR6包含问题中引用的Servlet规范部分的clarification:
澄清SRV 7.7.1“线程问题”(第33期)
更改当前
的段落“执行请求线程的多个servlet可以 可以同时有效访问单个会话对象。该 开发人员负责同步会话访问权限 资源酌情。“
阅读
“多个servlet正在执行 请求线程可以具有对同一会话对象的活动访问权限 同一时间。容器必须确保操纵内部 表示会话属性的数据结构在a中执行 线程安全的方式。开发人员负责线程安全 访问属性对象本身。这将保护 来自并发的HttpSession对象内的属性集合 访问,消除了应用程序导致这种情况的机会 收集变得腐败。“
这在Servlet 3.0 MR1中仍然是最新的,并且使WAS的行为看起来更合理。但是,我会从中得出* set * Attribute可能会被同步,但它不是* get *属性。
所以我认为答案是:
(作为旁注,更改测试用例以使listener.attributeAdded()调用setAttribute而不是getAttribute不会导致JBoss 4或5出现死锁。)
答案 1 :(得分:1)
您可能在IBM WebSphere特定实现中发现了HttpSession
不支持的用例。为什么不向IBM报告?
您实施时遗漏的一点:如果服务器必须在负载下处理太多会话,JavaEE容器可以钝化HttpSession
个对象(通过在磁盘或数据库上序列化)来释放内存。您的侦听器阻止垃圾收集器释放该会话。
顺便说一句,HttpSession
对象应该仅由与其自己的会话对应的线程使用。正如您在规范中发现的那样,如果来自同一会话的多个并发线程,代码必须使用HttpSession
对象上的同步机制。
会话侦听器是基于事件的,包含事件中的所有必要信息,这样的设计足以避免侦听器以您的方式保留对活HttpSession
个对象的所有引用。
从一个线程中查询容器中的所有生存会话都是奇怪且意外的。它不是Web应用程序的工作,而是监视或审计工具的工作。在这种情况下,应该使用其他方法,例如特定WebSphere上下文中的JMX查询或PMI接口。
为了帮助您,这里是您的侦听器的替代实现,以实现相同的会话属性计数,但不在HttpSession
上保留任何引用。注意:它既没有编译也没有经过测试。
public class SessionTestListener implements
HttpSessionListener, HttpSessionAttributeListener {
private static final Set<String> sessionsIds
= new ConcurrentSkipListSet<String>();
private static final ConcurrentMap<String, Object> sessionsKeys
= new ConcurrentHashMap<String, Object>();
public void attributeRemoved(HttpSessionBindingEvent hsbe) {
System.out.println("Attribute removed, " + hsbe.getName()
+ "=" + hsbe.getValue());
if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) {
sessionsKeys.remove(hsbe.getSession().getId());
}
}
public void attributeAdded(HttpSessionBindingEvent hsbe) {
System.out.println("Attribute added, " + hsbe.getName()
+ "=" + hsbe.getValue());
if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) {
if (hsbe.getValue() == null) {
sessionsKeys.remove(hsbe.getSession().getId());
} else {
sessionsKeys.put(hsbe.getSession().getId(), hsbe.getValue());
}
}
System.out.println(sessionsKeys.size() + " of " + sessionsIds.size()
+ " sessions have attribute set.");
}
public void attributeReplaced(HttpSessionBindingEvent hsbe) {}
public void sessionCreated(HttpSessionEvent hse) {
sessionsIds.add(hse.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent hse) {
sessionsIds.remove(hse.getSession().getId());
sessionsKeys.remove(hse.getSession().getId());
}
}