📄 轻松使用线程:同步不是敌人.htm
字号:
<TD width=6><IMG height=1 alt="" src="轻松使用线程:同步不是敌人.files/c.gif" width=6
border=0></TD></TR><!-- Black line separator -->
<TR vAlign=top>
<TD bgColor=#000000 colSpan=5><IMG height=1 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=100 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff colSpan=5><IMG height=8 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=100 border=0></TD></TR></TBODY></TABLE><!-- END HEADER AREA --><!-- START BODY AREA -->
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=10><IMG height=1 alt="" src="轻松使用线程:同步不是敌人.files/c.gif" width=10
border=0></TD>
<TD width="100%">
<TABLE cellSpacing=0 cellPadding=0 width=168 align=right border=0>
<TBODY>
<TR><!-- Sidebar Gutter -->
<TD width=8><IMG height=21 alt="" src="轻松使用线程:同步不是敌人.files/c.gif"
width=5></TD>
<TD width=160><!-- Start TOC -->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=轻松使用线程:同步不是敌人.files/bg-gold.gif
height=5><B>内容:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=160></TD></TR>
<TR>
<TD align=right>
<TABLE cellSpacing=0 cellPadding=3 width="98%" border=0>
<TBODY>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#1"><CODE>synchronized</CODE>
<I>真正</I>意味着什么?</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#2">使用一条好的运行路线</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#3">同步的代价有多大?</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#4">不要争用</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#5">什么时候需要同步?</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#6">考虑使用同步包装</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#7">结论</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#resources">参考资料</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#author1">关于作者</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#rating">对本文的评价</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End TOC --><!-- Start Related Content Area -->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=轻松使用线程:同步不是敌人.files/bg-gold.gif
height=5><B>相关内容:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt=""
src="轻松使用线程:同步不是敌人.files/c.gif" width=160></TD></TR>
<TR>
<TD align=right>
<TABLE cellSpacing=0 cellPadding=3 width="98%" border=0>
<TBODY>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-thread/index.shtml">编写多线程的
Java 应用程序</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/praxis/pr52.shtml">用固定的,循环的顺序获取多个锁定以避免死锁</A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/java/">更多的
dW Java 参考资料</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><!-- End Related dW Content Area -->
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2
alt="" src="轻松使用线程:同步不是敌人.files/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2
alt="" src="轻松使用线程:同步不是敌人.files/c.gif"
width=160></TD></TR></TBODY></TABLE><!-- END STANDARD SIDEBAR AREA --></TD></TR></TBODY></TABLE><SPAN
class=atitle2>我们什么时候需要同步,而同步的代价到底有多大?</SPAN>
<P><A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#author1">Brian
Goetz</A> (<A
href="mailto:brian@quiotix.com">brian@quiotix.com</A>)<BR>软件顾问,Quiotix<BR>2001
年 7 月</P>
<BLOCKQUOTE>
<P>与许多其它的编程语言不同,Java
语言规范包括对线程和并发的明确支持。语言本身支持并发,这使得指定和管理共享数据的约束以及跨线程操作的计时变得更简单,但是这没有使得并发编程的复杂性更易于理解。这个三部分的系列文章的目的在于帮助程序员理解用
Java 语言进行多线程编程的一些主要问题,特别是线程安全对 Java 程序性能的影响。</P>
<P>请点击文章顶部或底部的<B>讨论</B>进入由 Brian Goetz 主持的<A
href="http://www-105.ibm.com/developerWorks/java_df.nsf/AllViewTemplate?OpenForm&RestrictToCategory=23">
“Java
线程:技巧、窍门和技术”讨论论坛</A>,与本文作者和其他读者交流您对本文或整个多线程的想法。注意该论坛讨论的是使用多线程时遇到的所有问题,而并不限于本文的内容。</P></BLOCKQUOTE>
<P>大多数编程语言的语言规范都不会谈到线程和并发的问题;因为一直以来,这些问题都是留给平台或操作系统去详细说明的。但是,Java
语言规范(JLS)却明确包括一个线程模型,并提供了一些语言元素供开发人员使用以保证他们程序的线程安全。</P>
<P>对线程的明确支持有利也有弊。它使得我们在写程序时更容易利用线程的功能和便利,但同时也意味着我们不得不注意所写类的线程安全,因为任何类都很有可能被用在一个多线程的环境内。</P>
<P>许多用户第一次发现他们不得不去理解线程的概念的时候,并不是因为他们在写创建和管理线程的程序,而是因为他们正在用一个本身是多线程的工具或框架。任何用过
Swing GUI 框架或写过小服务程序或 JSP 页的开发人员(不管有没有意识到)都曾经被线程的复杂性困扰过。</P>
<P>Java
设计师是想创建一种语言,使之能够很好地运行在现代的硬件,包括多处理器系统上。要达到这一目的,管理线程间协调的工作主要推给了软件开发人员;程序员必须指定线程间共享数据的位置。在
Java 程序中,用来管理线程间协调工作的主要工具是 <CODE>synchronized</CODE> 关键字。在缺少同步的情况下,JVM
可以很自由地对不同线程内执行的操作进行计时和排序。在大部分情况下,这正是我们想要的,因为这样可以提高性能,但它也给程序员带来了额外的负担,他们不得不自己识别什么时候这种性能的提高会危及程序的正确性。</P>
<P><A id=1 name=1></A><SPAN class=atitle2><CODE>synchronized</CODE>
<I>真正</I>意味着什么?</SPAN><BR>大部分 Java
程序员对同步的块或方法的理解是完全根据使用互斥(互斥信号量)或定义一个临界段(一个必须原子性地执行的代码块)。虽然
<CODE>synchronized</CODE> 的语义中确实包括互斥和原子性,但在管程进入之前和在管程退出之后发生的事情要复杂得多。</P>
<P><CODE>synchronized</CODE>
的语义确实保证了一次只有一个线程可以访问被保护的区段,但同时还包括同步线程在主存内互相作用的规则。理解 Java
内存模型(JMM)的一个好方法就是把各个线程想像成运行在相互分离的处理器上,所有的处理器存取同一块主存空间,每个处理器有自己的缓存,但这些缓存可能并不总和主存同步。在缺少同步的情况下,JMM
会允许两个线程在同一个内存地址上看到不同的值。而当用一个管程(锁)进行同步的时候,一旦申请加了锁,JMM
就会马上要求该缓存失效,然后在它被释放前对它进行刷新(把修改过的内存位置写回主存)。不难看出为什么同步会对程序的性能影响这么大;频繁地刷新缓存代价会很大。</P>
<P><A id=2 name=2></A><SPAN
class=atitle2>使用一条好的运行路线</SPAN><BR>如果同步不适当,后果是很严重的:会造成数据混乱和争用情况,导致程序崩溃,产生不正确的结果,或者是不可预计的运行。更糟的是,这些情况可能很少发生且具有偶然性(使得问题很难被监测和重现)。如果测试环境和开发环境有很大的不同,无论是配置的不同,还是负荷的不同,都有可能使得这些问题在测试环境中根本不出现,从而得出错误的结论:我们的程序是正确的,而事实上这些问题只是还没出现而已。</P><!-- SIDEBAR -->
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=轻松使用线程:同步不是敌人.files/bg-gold.gif>
<P><B>争用情况定义</B><BR><I>争用情况</I>是一种特定的情况:两个或更多的线程或进程读或写一些共享数据,而最终结果取决于这些线程是如何被调度计时的。争用情况可能会导致不可预见的结果和隐蔽的程序错误。</P></TD></TR></TBODY></TABLE><!-- END OF SIDEBAR -->
<P>另一方面,不当或过度地使用同步会导致其它问题,比如性能很差和死锁。当然,性能差虽然不如数据混乱那么严重,但也是一个严重的问题,因此同样不可忽视。编写优秀的多线程程序需要使用好的运行路线,足够的同步可以使您的数据不发生混乱,但不需要滥用到去承担死锁或不必要地削弱程序性能的风险。</P>
<P><A id=3 name=3></A><SPAN
class=atitle2>同步的代价有多大?</SPAN><BR>由于包括缓存刷新和设置失效的过程,Java
语言中的同步块通常比许多平台提供的临界段设备代价更大,这些临界段通常是用一个原子性的“test and set
bit”机器指令实现的。即使一个程序只包括一个在单一处理器上运行的单线程,一个同步的方法调用仍要比非同步的方法调用慢。如果同步时还发生锁定争用,那么性能上付出的代价会大得多,因为会需要几个线程切换和系统调用。</P>
<P>幸运的是,随着每一版的 JVM 的不断改进,既提高了 Java
程序的总体性能,同时也相对减少了同步的代价,并且将来还可能会有进一步的改进。此外,同步的性能代价经常是被夸大的。一个著名的资料来源就曾经引证说一个同步的方法调用比一个非同步的方法调用慢
50 倍。虽然这句话有可能是真的,但也会产生误导,而且已经导致了许多开发人员即使在需要的时候也避免使用同步。</P>
<P>严格依照百分比计算同步的性能损失并没有多大意义,因为一个无争用的同步给一个块或方法带来的是固定的性能损失。而这一固定的延迟带来的性能损失百分比取决于在该同步块内做了多少工作。对一个<I>
空</I>方法的同步调用可能要比对一个空方法的非同步调用慢 20
倍,但我们多长时间才调用一次空方法呢?当我们用更有代表性的小方法来衡量同步损失时,百分数很快就下降到可以容忍的范围之内。</P>
<P>表 1 把一些这种数据放在一起来看。它列举了一些不同的实例,不同的平台和不同的 JVM
下一个同步的方法调用相对于一个非同步的方法调用的损失。在每一个实例下,我运行一个简单的程序,测定循环调用一个方法 10,000,000
次所需的运行时间,我调用了同步和非同步两个版本,并比较了结果。表格中的数据是同步版本的运行时间相对于非同步版本的运行时间的比率;它显示了同步的性能损失。每次运行调用的都是清单
1 中的简单方法之一。</P>
<P>表格 1 中显示了同步方法调用相对于非同步方法调用的相对性能;为了用绝对的标准测定性能损失,必须考虑到 JVM
速度提高的因素,这并没有在数据中体现出来。在大多数测试中,每个 JVM 的更高版本都会使 JVM 的总体性能得到很大提高,很有可能 1.4 版的
Java 虚拟机发行的时候,它的性能还会有进一步的提高。</P>
<P><B>表 1. 无争用同步的性能损失</B></P>
<TABLE cellSpacing=1 cellPadding=1 border=1>
<TBODY>
<TR>
<TH>JDK</TH>
<TH>staticEmpty</TH>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -