synchronized真正意味着什么?
synchronized 的语义确实保证了一次只有一个线程可以访问被保护的区段,但同时还包括同步线程在主存内互相作用的规则。理解 Java 内存模型(JMM)的一个好方法就是把各个线程想像成运行在相互分离的处理器上,所有的处理器存取同一块主存空间,每个处理器有自己的缓存,但这些缓存可能并不总和主存同步。在缺少同步的情况下,JMM 会允许两个线程在同一个内存地址上看到不同的值。而当用一个管程(锁)进行同步的时候,一旦申请加了锁,JMM 就会马上要求该缓存失效,然后在它被释放前对它进行刷新(把修改过的内存位置写回主存)。不难看出为什么同步会对程序的性能影响这么大,频繁地刷新缓存代 价会很大。
同步的代价有多大?
对非争用同步而言,虽然存在性能损失,但在运行许多不是特别微小的方法时,损失可以降到一个合理的水平;大多数情况下损失大概在 10% 到 200% 之间(这是一个相对较小的数目)。所以,虽然同步每个方法是不明智的(这也会增加死锁的可能性),但我们也不需要这么害怕同步。
由于早期的书籍和文章暗示了无争用同步要付出巨大的性能代价,许多程序员就竭尽全力避免同步。这种恐惧导致了许多有问题的技术出现,比如说double-checked locking(DCL)。许多关于Java编程的书和文章都推荐DCL,它看上去真是避免不必要的同步的一种聪明的方法,但实际上它根本没有用,应该避免使用它。
什么时候需要同步?
要使您的程序线程安全,首先必须确定哪些数据将在线程间共享。如果正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。有些程序员可能会惊讶地发现,这些规则在简单地 检查一个共享引用是否非空的时候也用得上。
许多人会发现这些定义惊人地严格。有一种普遍的观点是,如果只是要读一个对象的字段,不需要请求加锁,尤其是在JLS保证了32位读操作的原子性的情况下,它更是如此。但不幸的是,这个观点是错误的。除非所指的字段被声明为volatile,否则JMM不会要求下面的平台提供处理器间的缓存一致性和顺序连贯性,所以很有可能,在某些平台上,没有同步就会读到陈旧的数据。
在确定了要共享的数据之后,还要确定要如何保护那些数据。在简单情况下,只需把它们声明为volatile即可保护数据字段;在其它情况下,必须在读或写共享数据前请求加锁,一个很好的经验是明确指出使用什么锁来保护给定的字段或对象,并在你的代码里把它记录下来。
还有一点值得注意的是,简单地同步存取器方法(或声明下层的字段为volatile)可能并不足以保护一个共享字段。可以考虑下面的示例:
...
private int foo;
public synchronized int getFoo() { return foo; }
public synchronized void setFoo(int f) { foo = f; }
如果一个调用者想要增加 foo 属性值,以下完成该功能的代码就不是线程安全的:
...
setFoo(getFoo() + 1);
如果两个线程试图同时增加foo属性值,结果可能是 foo 的值增加了1或2,这由计时决定。调用者将需要同步一个锁,才能防止这种争用情况;一个好方法是在JavaDoc类中指定同步哪个锁,这样类的调用者就不需要自己猜了。
以上情况是一个很好的示例,说明我们应该注意多层次粒度的数据完整性;同步存取器方法确保调用者能够存取到一致的和最近版本的属性值,但如果希望属性的将来值与当前值一致,或多个属性间相互一致,我们就必须同步复合操作 ― 可能是在一个粗粒度的锁上。
争用情况定义
争用情况是一种特定的情况:两个或更多的线程或进程读或写一些共享数据,而最终结果取决于这些线程是如何被调度计时的。争用情况可能会导致不可预见的结果和隐蔽的程序错误。
尽量不要争用
假设同步使用正确,若线程真正参与争用加锁,您也能感受到同步对实际性能的影响。并且无争用同步和争用同步间的性能损失差别很大;一个简单的测试程序指出争用同步比无争用同步慢 50 倍。把这一事实和我们上面抽取的观察数据结合在一起,可以看出使用一个争用同步的代价至少相当于创建50个对象。
所以,在调试应用程序中同步的使用时,我们应该努力减少实际争用的数目,而根本不是简单地试图避免使用同步。这个系列的第2部分将把重点放在减少争用的技术上,包括减小锁的粒度、减小同步块的大小以及减小线程间共享数据的数量。
争用为什么慢?
争用同步之所以慢,是因为它涉及多个线程切换和系统调用。当多个线程争用同一个管程时,JVM将不得不维护一个等待该管程的线程队列(并且这个队列在多个处理器间必须是同步的),这就意味着花费在JVM或OS代码上的时间相对多了,而花费在程序代码上的时间则相对少了。而且,争用还削弱了可伸缩性,因为它迫使调度程序把操作序列化,即使有可用的空闲处理器也是如此。当一个线程正在执行一个同步块时,任何等待进入该块的线程都将被阻塞。如果没有其他的线程可供执行,那么处理器就将空闲。
如果想编写具可伸缩性的多线程程序,我们就必须减少对临界资源的争用。有很多技术可以做到这一点,但在应用它们之前,您需要仔细研究一下您的代码,判断出在什么情况下您需要在公共管程上同步。判断哪些锁是瓶颈很困难:有时候锁隐藏在类库中,有时候又通过同步方法隐式地指定,因此在阅读代码时,锁并不那么明显。而且,目前的争用检测工具也很差。
减少争用的方法
①“放进去,取出来”——使同步块尽可能小
使同步块尽可能小显然是降低争用可能性的一种技术。一个线程占用一个给定锁的时间越短,另一个线程在该线程仍占用锁时请求该锁的可能性就越小。因此在您应该使用同步去访问或更新共享变量时,在同步块的外面进行线程安全的预处理或后处理通常会更好些。
另一方面,有可能会过度使用这种技术。要是您想用一小块线程安全代码把要求同步的两个操作隔开,那么只使用一个同步块一般会更好些。
②减小锁的粒度
把您的同步分散在更多的锁上是减少争用的另一种有价值的技术。
如清单3所示的例子,虽然这样做是完全线程安全的,但却增加了毫无实际意义的争用可能性。如果一个线程正在执行setUserInfo ,就不仅意味着其它线程将被锁在 setUserInfo 和 getUserInfo 外面(这是我们希望的),而且意味着它们也将被锁在 getServiceInfo 和 setServiceInfo 外面。通过使访问器只在共享的实际对象( userMap 和 servicesMap 对象)上同步可以避免这个问题,如清单4所示。
现在,访问服务 map(servicesMap)的线程将不会与试图访问用户 map(usersMap)的线程发生争用。(在这种情况下,通过使用 Collections 框架提供的同步包装机制,即 Collections.synchronizedMap 来创建 map 可以达到同样的效果。)假设对两个 map 的请求是平均分布的,那么这种技术在这种情况下将把可能的争用数目减半。
③锁崩溃
另一种能提高性能的技术称为“锁崩溃”(请参阅清单 6)。回想一下, Vector 类的方法几乎都是同步的。假设您有一个 String 值的Vector ,并想搜索最长的 String 。进一步假设您已经知道只会在末端添加元素,而且元素不会被删除,那么,像 getLongest() 方法所展示的那样访问数据是安全的(通常),该方法只是调用 elementAt() 来检索每个元素,简单地对Vector 的元素作循环。
getLongest2()方法非常相似,除了在开始循环之前获取Vector上的锁之外。这样做的结果是当elementAt()试图获取锁时,JVM 将注意到当前线程已经拥有锁,而且将不会参与争用。getLongest2()加大了同步块,这似乎违背了“放进去,取出来”的原则,但因为避免了很大量可能的同步,调度开销的时间损失也少了,速度仍然快得多。
④在HashMap中应用技术②——减小锁的粒度
服务器端的Java应用程序中最普通的争用瓶颈之一是HashMap。应用程序使用HashMap来高速缓存所有类别的临界共享数据(用户概要文件、会话信息、文件内容),HashMap.get方法可能对应于许多条字节码指令。例如,如果您正在编写一个Web服务器,而且所有的高速缓存的页都存储在HashMap中,那么每个请求都将需要获得并占用那个map上的锁,这就将成为一个瓶颈。
我们可以扩展锁粒度技术以应付这种情形,尽管我们必须很小心,因为有与这种方法有关的一些Java内存模型(JavaMemoryModel,JMM)危害。清单5中的LockPoolMap展示了线程安全的get()和put()方法,但把同步分散在了锁池中,充分降低了争用可能性。
LockPoolMap是线程安全的,其功能类似于简化的HashMap,但却有更多吸引人的争用属性。同步不是在每个get()或put()操作上对整个map进行,而是在散列单元(bucket)级上完成。每个bucket都有一个锁,而且该锁在遍历bucket(为了读或写)的时候被获取。锁在创建map的时候被创建(如果不在此时创建锁,将会出现JMM问题。)
如果您创建了带有很多bucket的LockPoolMap,那么将有很多线程可以并发地使用该map,同时争用的可能性也被大大降低了。然而,减少争用并不是免费的午餐。由于没有在全局锁上同步,使得将map作为一个整体进行操作,例如size()方法,变得更加困难。size()的实现将不得不依次获取各个bucket的锁,对该bucket中的节点进行计数,释放锁,然后继续到下一个bucket。然而前面的锁一旦被释放,其它的线程就将可以自由修改前面的bucket。到size()完成对元素的计数时,结果很可能是错的。不过,LockPoolMap技术在某些方面还是可以做得相当好的,例如共享高速缓存。
清单 5. 减小HashMap上锁的粒度
import java.util.*; /** * LockPoolMap implements a subset of the Map interface (get, put, clear) * and performs synchronization at the bucket level, not at the map * level. This reduces contention, at the cost of losing some Map * functionality, and is well suited to simple caches. The number of * buckets is fixed and does not increase. */ public class LockPoolMap { private Node[] buckets; private Object[] locks; private static final class Node { public final Object key; public Object value; public Node next; public Node(Object key) { this.key = key; } } public LockPoolMap(int size) { buckets = new Node[size]; locks = new Object[size]; for (int i = 0; i < size; i++) locks[i] = new Object(); } private final int hash(Object key) { int hash = key.hashCode() % buckets.length; if (hash < 0) hash *= -1; return hash; } public void put(Object key, Object value) { int hash = hash(key); synchronized(locks[hash]) { Node m; for (m=buckets[hash]; m != null; m=m.next) { if (m.key.equals(key)) { m.value = value; return; } } // We must not have found it, so put it at the beginning of the chain 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; } }
摘抄自IBM developerWorks上的文章——《轻松使用线程》系列:
相关推荐
下面将详细阐述这款软件的主要功能、工作原理以及如何使用。 一、主要功能 1. 屏幕抓取:汉王屏幕摘抄精灵具备强大的屏幕捕捉功能,用户可以轻松选取屏幕上的任何文本区域,无论是网页、PDF文档还是图片,都能进行...
经典IT文章摘抄经典IT文章摘抄经典IT文章摘抄经典IT文章摘抄经典IT文章摘抄
在处理PDF文件时,汉王屏幕摘抄精灵能够有效地识别和解析PDF中的文字,无论这些文字是扫描图像还是嵌入式字体。对于扫描PDF,即由纸质文档扫描生成的图像PDF,汉王软件通过先进的图像处理和文字识别算法,能准确地...
【初一摘抄好词好句大全】是一个文档,主要收集了适合初一学生学习和借鉴的优美词汇和句子。这些摘抄对于提高学生的语文素养、丰富写作素材有着积极的作用。以下是对其中部分好词好句的详细解读: 1. **好词**:...
【TCP/IP路由技术】是网络...吴大卫的摘抄笔记可能涵盖了这些基本概念的详细解释,以及相关的实践案例和问题解决技巧。通过深入学习这些内容,网络从业人员能够提升对网络路由技术的理解,提高网络故障排查和优化能力。
总结起来,"读书笔记摘抄大全"中提到的书籍《爱的教育》和《做最好的自己》都强调了爱、关怀他人以及自我成长的重要性。《爱的教育》通过故事展现了关爱与教育的力量,提醒我们在现代社会中保持善良与宽容。而《做...
高考议论文素材摘抄三则 高考议论文名人素材摘抄精选.docx
"摘抄好词好句好段-好词好句好段摘抄大全.doc" 本文档为一份摘抄大全,收录了大量的好词好句好段,具有很高的参考价值。下面将对标题、描述、标签和部分内容进行详细的解释和分析。 标题:摘抄好词好句好段-好词...
青铜葵花好词好句摘抄.doc
艾青诗选摘抄20篇.doc
星火英语美文听力4篇摘抄参考.doc
美文摘抄600字.doc
【小学生好词好句摘抄大全】是一份适合小学生学习和积累写作素材的文档,包含了丰富的形容词和生动的句子,旨在提升学生的语言表达能力和写作水平。以下是对这些好词好句的详细解读: 1. **勤奋、刻苦、认真、专注...
总的来说,汉王摘抄是一款结合了OCR技术、文字识别和翻译功能的手机应用,旨在帮助用户更高效地处理和利用文字信息。无论是学术研究、工作记录还是日常生活,这款软件都能提供极大的便利,减少手动输入的工作量,...
斯宾塞的快乐教育读书摘抄.docx
四年级作文摘抄20篇.doc
这些摘抄和语录共同传达了一个信息:生活不仅是物质的追求,更是精神的探索和提升。无论是余秋雨的历史沉思,还是汪国真的诗意激励,或是毕淑敏的心理洞察,都在引导读者思考人生的意义,鼓励我们在生活中保持热爱、...
教师读书笔记摘抄及感悟.doc
摘抄的句子涵盖了生活、励志和学习等多个方面,旨在提升学生的英语阅读理解能力、词汇量以及对英语语境的理解。 在【篇一】中,我们可以看到一些富有哲理的句子,这些句子不仅适用于英语学习,也对学生的人生观有所...
易中天中国智慧资料经典语句摘抄.doc