📄 轻松使用线程:同步不是敌人.htm
字号:
<TH>empty</TH>
<TH>fetch</TH>
<TH>hashmapGet</TH>
<TH>singleton</TH>
<TH>create</TH></TR>
<TR>
<TD>Linux / JDK 1.1</TD>
<TD>9.2</TD>
<TD>2.4</TD>
<TD>2.5</TD>
<TD>n/a</TD>
<TD>2.0</TD>
<TD>1.42</TD></TR>
<TR>
<TD>Linux / IBM Java SDK 1.1</TD>
<TD>33.9</TD>
<TD>18.4</TD>
<TD>14.1</TD>
<TD>n/a</TD>
<TD>6.9</TD>
<TD>1.2</TD></TR>
<TR>
<TD>Linux / JDK 1.2</TD>
<TD>2.5</TD>
<TD>2.2</TD>
<TD>2.2</TD>
<TD>1.64</TD>
<TD>2.2</TD>
<TD>1.4</TD></TR>
<TR>
<TD>Linux / JDK 1.3 (no JIT)</TD>
<TD>2.52</TD>
<TD>2.58</TD>
<TD>2.02</TD>
<TD>1.44</TD>
<TD>1.4</TD>
<TD>1.1</TD></TR>
<TR>
<TD>Linux / JDK 1.3 -server</TD>
<TD>28.9</TD>
<TD>21.0</TD>
<TD>39.0</TD>
<TD>1.87</TD>
<TD>9.0</TD>
<TD>2.3</TD></TR>
<TR>
<TD>Linux / JDK 1.3 -client</TD>
<TD>21.2</TD>
<TD>4.2</TD>
<TD>4.3</TD>
<TD>1.7</TD>
<TD>5.2</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Linux / IBM Java SDK 1.3</TD>
<TD>8.2</TD>
<TD>33.4</TD>
<TD>33.4</TD>
<TD>1.7</TD>
<TD>20.7</TD>
<TD>35.3</TD></TR>
<TR>
<TD>Linux / gcj 3.0</TD>
<TD>2.1</TD>
<TD>3.6</TD>
<TD>3.3</TD>
<TD>1.2</TD>
<TD>2.4</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.1</TD>
<TD>38.6</TD>
<TD>20.1</TD>
<TD>12.8</TD>
<TD>n/a</TD>
<TD>11.8</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.2</TD>
<TD>39.2</TD>
<TD>8.6</TD>
<TD>5.0</TD>
<TD>1.4</TD>
<TD>3.1</TD>
<TD>3.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 (no JIT)</TD>
<TD>2.0</TD>
<TD>1.8</TD>
<TD>1.8</TD>
<TD>1.0</TD>
<TD>1.2</TD>
<TD>1.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 -client</TD>
<TD>19.8</TD>
<TD>1.5</TD>
<TD>1.1</TD>
<TD>1.3</TD>
<TD>2.1</TD>
<TD>1.7</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 -server</TD>
<TD>1.8</TD>
<TD>2.3</TD>
<TD>53.0</TD>
<TD>1.3</TD>
<TD>4.2</TD>
<TD>3.2</TD></TR></TBODY></TABLE>
<P><B>清单 1. 基准测试中用到的简单方法</B></P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE> public static void staticEmpty() { }
public void empty() { }
public Object fetch() { return field; }
public Object singleton() {
if (singletonField == null)
singletonField = new Object();
return singletonField;
}
public Object hashmapGet() {
return hashMap.get("this");
}
public Object create() {
return new Object();
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>
<P>这些小基准测试也阐明了存在动态编译器的情况下解释性能结果所面临的挑战。对于 1.3 JDK 在有和没有 JIT
时,数字上的巨大差异需要给出一些解释。对那些非常简单的方法(<CODE>empty</CODE> 和
<CODE>fetch</CODE>),基准测试的本质(它只是执行一个几乎什么也不做的紧凑的循环)使得 JIT
可以动态地编译整个循环,把运行时间压缩到几乎没有的地步。但在一个实际的程序中,JIT 能否这样做就要取决于很多因素了,所以,无 JIT
的计时数据可能在做公平对比时更有用一些。在任何情况下,对于更充实的方法(<CODE> create</CODE> 和
<CODE>hashmapGet</CODE>),JIT 就不能象对更简单些的方法那样使非同步的情况得到巨大的改进。另外,从数据中看不出 JVM
是否能够对测试的重要部分进行优化。同样,在可比较的 IBM 和 Sun JDK 之间的差异反映了 IBM Java SDK
可以更大程度地优化非同步的循环,而不是同步版本代价更高。这在纯计时数据中可以明显地看出(这里不提供)。</P>
<P>从这些数字中我们可以得出以下结论:对非争用同步而言,虽然存在性能损失,但在运行许多不是特别微小的方法时,损失可以降到一个合理的水平;大多数情况下损失大概在
10% 到 200%
之间(这是一个相对较小的数目)。所以,虽然同步每个方法是不明智的(这也会增加死锁的可能性),但我们也不需要这么害怕同步。这里使用的简单测试是说明一个无争用同步的代价要比创建一个对象或查找一个
<CODE>HashMap</CODE> 的代价小。</P>
<P>由于早期的书籍和文章暗示了无争用同步要付出巨大的性能代价,许多程序员就竭尽全力避免同步。这种恐惧导致了许多有问题的技术出现,比如说
double-checked locking(DCL)。许多关于 Java 编程的书和文章都推荐
DCL,它看上去真是避免不必要的同步的一种聪明的方法,但实际上它根本没有用,应该避免使用它。DCL
无效的原因很复杂,已超出了本文讨论的范围(要深入了解,请参阅<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#resources">参考资料</A>里的链接)。</P>
<P><A id=4 name=4></A><SPAN
class=atitle2>不要争用</SPAN><BR>假设同步使用正确,若线程真正参与争用加锁,您也能感受到同步对实际性能的影响。并且无争用同步和争用同步间的性能损失差别很大;一个简单的测试程序指出争用同步比无争用同步慢
50 倍。把这一事实和我们上面抽取的观察数据结合在一起,可以看出使用一个争用同步的代价至少相当于创建 50 个对象。</P>
<P>所以,在调试应用程序中同步的使用时,我们应该努力减少实际争用的数目,而根本不是简单地试图避免使用同步。这个系列的第 2
部分将把重点放在减少争用的技术上,包括减小锁的粒度、减小同步块的大小以及减小线程间共享数据的数量。</P>
<P><A id=5 name=5></A><SPAN
class=atitle2>什么时候需要同步?</SPAN><BR>要使您的程序线程安全,首先必须确定哪些数据将在线程间共享。如果正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。有些程序员可能会惊讶地发现,这些规则在简单地检查一个共享引用是否非空的时候也用得上。</P>
<P>许多人会发现这些定义惊人地严格。有一种普遍的观点是,如果只是要读一个对象的字段,不需要请求加锁,尤其是在 JLS 保证了 32
位读操作的原子性的情况下,它更是如此。但不幸的是,这个观点是错误的。除非所指的字段被声明为 <CODE>volatile</CODE>,否则 JMM
不会要求下面的平台提供处理器间的缓存一致性和顺序连贯性,所以很有可能,在某些平台上,没有同步就会读到陈旧的数据。有关更详细的信息,请参阅<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-threads/index.shtml#resources">参考资料</A>。</P>
<P>在确定了要共享的数据之后,还要确定要如何保护那些数据。在简单情况下,只需把它们声明为 <CODE>volatile</CODE>
即可保护数据字段;在其它情况下,必须在读或写共享数据前请求加锁,一个很好的经验是明确指出使用什么锁来保护给定的字段或对象,并在你的代码里把它记录下来。</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -