📄 轻松使用线程:减少争用.htm
字号:
m = new Node(key);
m.value = value;
m.next = buckets[hash];
buckets[hash] = m;
}
}
public Object get(Object key) {
int hash = hash(key);
synchronized(locks[hash]) {
for (Node m=buckets[hash]; m != null; m=m.next)
if (m.key.equals(key))
return m.value;
}
return null;
}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>表 1 比较了共享 map 的三种实现的性能:同步的 <CODE>HashMap</CODE>,非同步的
<CODE>HashMap</CODE>(线程不安全的)和
<CODE>LockPoolMap</CODE>。提供非同步的版本只是为了展示争用的开销。我们在使用 Sun 1.3 JDK 的双处理器系统
Linux 系统上,用不同数目的线程,运行了在 map 上执行随机进行 <CODE>put()</CODE> 和
<CODE>get()</CODE> 操作的测试。该表展示了每个组合的运行时间。这个测试是有点极端的一个案例,测试程序只是访问
map,而不做任何别的事,因此它比实际的程序存在多得多的争用,设计这个测试只是为了说明争用对性能的损害。</P>
<P><B>表 1. <CODE>HashMap</CODE> 和 <CODE>LockPoolMap</CODE> 之间的可伸缩性比较</B>
<TABLE cellSpacing=1 cellPadding=1 border=1>
<TBODY>
<TR>
<TH>线程</TH>
<TH>非同步的 <CODE>HashMap</CODE> (不安全的)</TH>
<TH>同步的 <CODE>HashMap</CODE></TH>
<TH><CODE>LockPoolMap</CODE></TH></TR>
<TR>
<TD></TD>
<TD>1.1</TD>
<TD>1.4</TD>
<TD>1.6</TD></TR>
<TR>
<TD></TD>
<TD>1.1</TD>
<TD>57.6</TD>
<TD>3.7</TD></TR>
<TR>
<TD></TD>
<TD>2.1</TD>
<TD>123.5</TD>
<TD>7.7</TD></TR>
<TR>
<TD></TD>
<TD>3.7</TD>
<TD>272.3</TD>
<TD>16.7</TD></TR>
<TR>
<TD>16</TD>
<TD>6.8</TD>
<TD>577.0</TD>
<TD>37.9</TD></TR>
<TR>
<TD>32</TD>
<TD>13.5</TD>
<TD>1233.3</TD>
<TD>80.5</TD></TR></TBODY></TABLE></P>
<P>虽然在线程数量很多的情况下,所有的实现都表现出相似的伸缩性特征,但 <CODE>HashMap</CODE>
实现在从一个线程变到两个线程时却表现出对性能影响的巨大变化,因为此时每一个 <CODE>put()</CODE> 和
<CODE>get()</CODE> 操作都存在争用。在线程数大于 1 时,<CODE>LockPoolMap</CODE> 技术几乎比
<CODE>HashMap</CODE> 技术快 15
倍。这个差别反映了调度开销上的时间损失和用于等待获取锁的空闲时间。<CODE>LockPoolMap</CODE>
的优势在拥有更多处理器的系统中将表现得更加明显。</P>
<P><A name=5><SPAN class=atitle2>技术
3:锁崩溃</SPAN></A><BR>另一种能提高性能的技术称为“锁崩溃”(请参阅清单 6)。回想一下,<CODE>Vector</CODE>
类的方法几乎都是同步的。假设您有一个 <CODE>String</CODE> 值的 <CODE>Vector</CODE>,并想搜索最长的
<CODE>String</CODE>。进一步假设您已经知道只会在末端添加元素,而且元素不会被删除,那么,像
<CODE>getLongest()</CODE>
方法所展示的那样访问数据是安全的(通常),该方法只是调用<CODE>elementAt()</CODE> 来检索每个元素,简单地对
<CODE>Vector</CODE> 的元素作循环。</P>
<P><CODE>getLongest2()</CODE> 方法非常相似,除了在开始循环之前获取 <CODE>Vector</CODE>
上的锁之外。这样做的结果是当 <CODE>elementAt()</CODE> 试图获取锁时,JVM
将注意到当前线程已经拥有锁,而且将不会参与争用。<CODE>getLongest2()</CODE>
加大了同步块,这似乎违背了“放进去,取出来”的原则,但因为避免了很大量可能的同步,调度开销的时间损失也少了,速度仍然快得多。</P>
<P>在运行 Sun 1.3 JDK 的双处理器 Linux 系统上,拥有两个线程,仅仅循环调用
<CODE>getLongest2()</CODE> 的的测试程序比调用 <CODE>getLongest()</CODE> 的要快 10
倍以上。虽然两个程序的序列化程度相同,但前者调度开销的时间损失要少得多。这又是一个极端的示例,但它表明争用的调度开销并不是微不足道的。即使只运行一个线程,崩溃版的速度也要快约
30% :获取您已占用的锁比获取无人占用的锁要快得多。</P><A name=listing6><B>清单 6. 锁崩溃</B></A>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
Vector v;
...
public String getLongest() {
int maxLen = 0;
String longest = null;
for (int i=0; i<v.size(); i++) {
String s = (String) v.elementAt(i);
if (s.length() > maxLen) {
maxLen = s.length();
longest = s;
}
}
return longest;
}
public String getLongest2() {
int maxLen = 0;
String longest = null;
synchronized (v) {
for (int i=0; i<v.size(); i++) {
String s = (String) v.elementAt(i);
if (s.length() > maxLen) {
maxLen = s.length();
longest = s;
}
}
return longest;
}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=6><SPAN
class=atitle2>结论</SPAN></A><BR>争用同步会严重影响程序的可伸缩性。更糟的是,除非您进行实际的负载测试,否则与争用相关的性能问题并不总是会在开发和测试过程中表现出来。本文提供的技术能有效地降低程序的争用代价,并增大程序在出现非线性伸缩行为之前所能承受的负载。但在应用这些技术之前,您必须首先分析您的程序,以判断哪里可能出现争用。
</P>
<P>在本系列的最后一部分,我们将讨论 <CODE>ThreadLocal</CODE>,它是 Thread API
中经常被忽视的一个工具。通过使用
<CODE>ThreadLocal</CODE>,给予每个线程它自己的特定临界对象的副本,我们就可以减少争用。别走开哦! </P>
<P><A name=resources><SPAN class=atitle2>参考资料</SPAN></A>
<UL>
<LI>参加 developerWorks 上 Brian 的<A
href="javascript:void%20forumWindow()">多线程 Java
编程讨论论坛</A>,获得关于线程和并发问题的帮助。<BR><BR>
<LI><I>轻松使用线程</I>系列的<A
href="http://www-900.ibm.com/developerWorks/cn/j-threads/java/index.shtml">第
1 部分</A>描述了无争用同步对性能的影响为何并不像普遍认为的那样严重。<BR><BR>
<LI>Jack Shirazi 的 <A
href="http://www.amazon.com/exec/obidos/ASIN/0596000154/none0b69"><I>Java
Performance Tuning</I></A>(O'Reilly & Associates,2000)就减少 Java
平台中性能方面的问题提供了指导。<BR><BR>
<LI>Steve Wilson 和 Jeff Kesselman 的<A
href="http://www.amazon.com/exec/obidos/ASIN/0201709694/none0b69">
<I>Java Platform Performance: Strategies and
Tactics</I></A>(Addison-Wesley,2000)为经验丰富的 Java
程序员提供了构建快速、高效代码的技术。<BR><BR>
<LI>Dov Bulka 的 <A
href="http://www.amazon.com/exec/obidos/ASIN/0201704293/none0b69"><I>Java
Performance and Scalability, Volume 1: Server-Side Programming
Techniques</I></A>(Addison-Wesley,2000)在提高您应用程序的性能方面提供了丰富的技巧和窍门。<BR><BR>
<LI>Brian Goetz 最近的文章 <A
href="http://www.javaworld.com/jw-02-2001/jw-0209-double.html">Double-checked
locking: Clever, but broken</A>(<I>JavaWorld</I>,2001 年 2 月)详细探讨了 JMM
以及在特定情况下同步失败导致的可怕后果。<BR><BR>
<LI>Doug Lea 的 <A
href="http://www.amazon.com/exec/obidos/ASIN/0201310090/none0b69"><I>Concurrent
Programming in Java, Second Edition</I></A>(Addison-Wesley,1999)是关于与
Java 多线程编程方面有关的微妙问题的一本权威书籍。<BR><BR>
<LI>Alex Roetter 在其文章<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-thread/index.shtml">编写多线程
Java 应用程序</A>中(developerWorks,2001 年 2 月)介绍了 Java Thread
API,略述了多线程的有关问题,并为常见问题提供了解决方案。<BR><BR>
<LI>IBM 提供的这篇文章描述了 <A
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=www7.software.ibm.com/vad.nsf/Data/Document2351?OpenDocument&p=1&BCT=3&Footer=1&origin=j">WebSphere
中的优化加锁</A>如何让不同的事务并发地读取同一个状态,如何只在更新的时候校验数据的完整性。<BR><BR>
<LI>IBM Thomas J. Watson 研究中心的<A
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=researchweb.watson.ibm.com/compsci/performance/&origin=j">性能建模和分析</A>小组正在研究性能和性能管理领域的几项工程。<BR><BR>
<LI>在 <A
href="http://www-900.ibm.com/developerWorks/cn/java/index.shtml">developerWorks
Java 技术专区</A>查找更多的 Java 参考资料。</LI></UL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1><SPAN class=atitle2>关于作者</SPAN><BR>Brian Goetz
是一名软件顾问,过去 15 年来一直是专业软件开发者。他是 <A
href="http://www.quiotix.com/">Quiotix</A>
的首席顾问,该公司从事软件开发和咨询业务,位于加利福尼亚的 Los Altos。敬请查看 Brian 在流行的业界出版物中<A
href="http://www.quiotix.com/~brian/pubs.html">已发表和即将发表的论文</A>列表。可通过
<A href="mailto:brian@quiotix.com">brian@quiotix.com</A> 与 Brian 联系。
</A></TD></TR></TBODY></TABLE></TD>
<TD width=10><IMG height=1 alt="" src="轻松使用线程:减少争用.files/c.gif" width=10
border=0></TD></TR></TBODY></TABLE><!-- END PAPER BODY--><BR clear=all><IMG
height=10 alt="" src="轻松使用线程:减少争用.files/c.gif" width=100 border=0><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD align=right width="100%"><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index2.shtml#top">到页首</A></TD>
<TD width=5><IMG height=1 alt="" src="轻松使用线程:减少争用.files/c.gif" width=5
border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#000000 colSpan=2><IMG height=1 alt=""
src="轻松使用线程:减少争用.files/c.gif" width=100 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff colSpan=2><IMG height=8 alt=""
src="轻松使用线程:减少争用.files/c.gif" width=100 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=10 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD>
<FORM action=/developerWorks/cn/cnratings.nsf/RateArticle?CreateDocument
method=post><INPUT type=hidden
value="Threading lightly : Reducing contention" name=ArticleTitle> <INPUT
type=hidden value=Java name=Zone> <INPUT type=hidden
value=/developerWorks/cn/thankyou/feedback-java.html name=RedirectURL> <A
name=rating><B>您对这篇文章的看法如何?</B></A>
<TABLE cellSpacing=0 cellPadding=0 width=600 border=0>
<TBODY>
<TR>
<TD colSpan=5><IMG height=8 alt="" src="轻松使用线程:减少争用.files/c.gif"
width=100 border=0></TD></TR>
<TR vAlign=top>
<TD width="16%"><INPUT type=radio value=5 name=Rating>真棒!(5)</TD>
<TD width="20%"><INPUT type=radio value=4 name=Rating>好材料 (4)</TD>
<TD width="24%"><INPUT type=radio value=3 name=Rating>一般;尚可 (3)</TD>
<TD width="22%"><INPUT type=radio value=2 name=Rating>需提高 (2)</TD>
<TD width="18%"><INPUT type=radio value=1 name=Rating>太差!
(1)</TD></TR></TBODY></TABLE><BR><B>建议?</B><BR><TEXTAREA name=Comments rows=5 wrap=virtual cols=60></TEXTAREA><BR><BR><INPUT type=submit value=提交反馈意见></FORM></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff><IMG height=8 alt="" src="轻松使用线程:减少争用.files/c.gif"
width=100 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD align=right>(c) Copyright IBM Corp. 2001, (c) Copyright IBM China
2001, All Right Reserved</TD></TR>
<TR vAlign=top>
<TD class=bbg height=21> <A class=mainlink
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=www-900.ibm.com/cn/ibm/index.shtml&origin=dwhead">关于
IBM</A><SPAN class=divider> | </SPAN><A
class=mainlink
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=www-900.ibm.com/cn/ibm/privacy/index.shtml&origin=dwhead">隐私条约</A><SPAN
class=divider> | </SPAN><A class=mainlink
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=www-900.ibm.com/cn/ibm/legal/index.shtml&origin=dwhead">使用条款</A><SPAN
class=divider> | </SPAN><A class=mainlink
href="http://www-900.ibm.com/developerWorks/cn/cgi-bin/click.cgi?url=www-900.ibm.com/cn/ibm/contact/index.shtml&origin=dwhead">联系
IBM</A></TD></TR></TBODY></TABLE>
<SCRIPT language=JavaScript1.2 src="轻松使用线程:减少争用.files/stats.js"
type=text/javascript></SCRIPT>
<NOSCRIPT><IMG height=1 alt="" src="D:\专业\JAVA\J2SE\轻松使用线程:减少争用.files\c(1).gif"
width=1 border=0></NOSCRIPT> </A></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -