和 CopyOnWriteArrayList 提供线程安全性和已改进的可伸缩性15036.docx
《和 CopyOnWriteArrayList 提供线程安全性和已改进的可伸缩性15036.docx》由会员分享,可在线阅读,更多相关《和 CopyOnWriteArrayList 提供线程安全性和已改进的可伸缩性15036.docx(18页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、eJava 理论与实践: 并发集合类ConcurrentHashMap 和 CopyOnWriteArrayList 提供线程安全性和已改进的可伸缩性级别: 初级Briaan GGoettz (briianquiiotiix.ccom), 首首席顾问问, QQuiootixx Coorp20033 年 9 月月 288 日DouggLeaa的 uutill.cooncuurreent 包除了了包含许许多其他他有用的的并发构构造块之之外,还还包含了了一些主主要集合合类型 Lisst 和和 Maap 的的高性能能的、线线程安全全的实现现。在本本月的 Javva理论论与实践践中,BBriaanGoo
2、etzz向您展展示了用用 CooncuurreentHHashhMapp 替换换 Haashttablle 或或 syynchhronnizeedMaap ,将将有多少少并发程程序获益益。您可可以在本本文的 论坛中与与作者以以及其他他读者共共享您的的想法(您您也可以以点击文文章顶部部或者底底部的 讨论进入入论坛)。 在Javva类库库中出现现的第一一个关联联的集合合类是 Hasshtaablee ,它它是JDDK 11.0的的一部分分。 HHashhtabble 提供了了一种易易于使用用的、线线程安全全的、关关联的mmap功功能,这这当然也也是方便便的。然然而,线线程安全全性是凭凭代价换换来的
3、 HHashhtabble 的所有有方法都都是同步步的。此此时,无无竞争的的同步会会导致可可观的性性能代价价。 HHashhtabble 的后继继者 HHashhMapp 是作作为JDDK1.2中的的集合框框架的一一部分出出现的,它它通过提提供一个个不同步步的基类类和一个个同步的的包装器器 Coolleectiionss.syynchhronnizeedMaap ,解解决了线线程安全全性问题题。通过过将基本本的功能能从线程程安全性性中分离离开来, Colllecctioons.synnchrroniizeddMapp 允许许需要同同步的用用户可以以拥有同同步,而而不需要要同步的的用户则则不必为
4、为同步付付出代价价。 Hashhtabble 和 ssyncchroonizzedMMap 所采取取的获得得同步的的简单方方法(同同步 HHashhtabble 中或者者同步的的 Maap 包包装器对对象中的的每个方方法)有有两个主主要的不不足。首首先,这这种方法法对于可可伸缩性性是一种种障碍,因因为一次次只能有有一个线线程可以以访问hhashh表。同同时,这这样仍不不足以提提供真正正的线程程安全性性,许多多公用的的混合操操作仍然然需要额额外的同同步。虽虽然诸如如 geet() 和 putt() 之类的的简单操操作可以以在不需需要额外外同步的的情况下下安全地地完成,但但还是有有一些公公用的操操
5、作序列列,例如如迭代或或者puut-iif-aabseent(空空则放入入),需需要外部部的同步步,以避避免数据据争用。 有条件的的线程安安全性同步的集集合包装装器 ssyncchroonizzedMMap 和 ssyncchroonizzedLListt ,有有时也被被称作 有条件件地线程程安全所有有单个的的操作都都是线程程安全的的,但是是多个操操作组成成的操作作序列却却可能导导致数据据争用,因因为在操操作序列列中控制制流取决决于前面面操作的的结果。 清单11中第一一片段展展示了公公用的pput-if-abssentt语句块块如如果一个个条目不不在 MMap 中,那那么添加加这个条条目。不不
6、幸的是是,在 conntaiinsKKey() 方方法返回回到 pput() 方方法被调调用这段段时间内内,可能能会有另另一个线线程也插插入一个个带有相相同键的的值。如如果您想想确保只只有一次次插入,您您需要用用一个对对 Maap mm 进行行同步的的同步块块将这一一对语句句包装起起来。 清单1中中其他的的例子与与迭代有有关。在在第一个个例子中中, LListt.siize() 的的结果在在循环的的执行期期间可能能会变得得无效,因因为另一一个线程程可以从从这个列列表中删删除条目目。如果果时机不不得当,在在刚好进进入循环环的最后后一次迭迭代之后后有一个个条目被被另一个个线程删删除了,则则 Lii
7、st.gett() 将返回回 nuull ,而 doSSomeethiing() 则则很可能能会抛出出一个 NulllPoointterEExceeptiion 异常。那那么,采采取什么么措施才才能避免免这种情情况呢?如果当当您正在在迭代一一个 LListt 时另另一个线线程也可可能正在在访问这这个 LListt ,那那么在进进行迭代代时您必必须使用用一个 synnchrroniizedd 块将将这个 Lisst 包包装起来来,在 Lisst 11 上同同步,从从而锁住住整个 Lisst 。这这样做虽虽然解决决了数据据争用问问题,但但是在并并发性方方面付出出了更多多的代价价,因为为在迭代代期间
8、锁锁住整个个 Liist 会阻塞塞其他线线程,使使它们在在很长一一段时间间内不能能访问这这个列表表。 集合框架架引入了了迭代器器,用于于遍历一一个列表表或者其其他集合合,从而而优化了了对一个个集合中中的元素素进行迭迭代的过过程。然然而,在在 jaava.utiil 集集合类中中实现的的迭代器器极易崩崩溃,也也就是说说,如果果在一个个线程正正在通过过一个 Iteerattor 遍历集集合时,另另一个线线程也来来修改这这个集合合,那么么接下来来的 IIterratoor.hhasNNextt() 或 IIterratoor.nnextt() 调用将将抛出 ConncurrrenntMoodiffi
9、caatioonExxcepptioon 异异常。就就拿刚才才这个例例子来讲讲,如果果想要防防止出现现 CooncuurreentMModiificcatiionEExceeptiion 异常,那那么当您您正在进进行迭代代时,您您必须使使用一个个在 LListt l 上同步步的 ssyncchroonizzed 块将该该 Liist 包装起起来,从从而锁住住整个 Lisst 。(或或者,您您也可以以调用 Lisst.ttoArrrayy() ,在不不同步的的情况下下对数组组进行迭迭代,但但是如果果列表比比较大的的话这样样做代价价很高)。 清单 11. 同同步的mmap中中的公用用竞争条条件 M
10、ap m = Collections.synchronizedMap(new HashMap(); List l = Collections.synchronizedList(new ArrayList(); / put-if-absent idiom - contains a race condition / may require external synchronization if (!map.containsKey(key) map.put(key, value); / ad-hoc iteration - contains race conditions / may require
11、 external synchronization for (int i=0; ilist.size(); i+) doSomething(list.get(i); / normal iteration - can throw ConcurrentModificationException / may require external synchronization for (Iterator i=list.iterator(); i.hasNext(); ) doSomething(i.next(); 信任的错错觉syncchroonizzedLListt 和 synnchrroniized
12、dMapp 提供供的有条条件的线线程安全全性也带带来了一一个隐患患 开发发者会假假设,因因为这些些集合都都是同步步的,所所以它们们都是线线程安全全的,这这样一来来他们对对于正确确地同步步混合操操作这件件事就会会疏忽。其其结果是是尽管表表面上这这些程序序在负载载较轻的的时候能能够正常常工作,但但是一旦旦负载较较重,它它们就会会开始抛抛出 NNulllPoiinteerExxcepptioon 或或 CooncuurreentMModiificcatiionEExceeptiion。可伸缩性性问题可伸缩性性指的是是一个应应用程序序在工作作负载和和可用处处理资源源增加时时其吞吐吐量的表表现情况况。一
13、个个可伸缩缩的程序序能够通通过使用用更多的的处理器器、内存存或者II/O带带宽来相相应地处处理更大大的工作作负载。锁锁住某个个共享的的资源以以获得独独占式的的访问这这种做法法会形成成可伸缩缩性瓶颈颈它它使其他他线程不不能访问问那个资资源,即即使有空空闲的处处理器可可以调用用那些线线程也无无济于事事。为了了取得可可伸缩性性,我们们必须消消除或者者减少我我们对独独占式资资源锁的的依赖。同步的集集合包装装器以及及早期的的 Haashttablle 和和 Veectoor 类类带来的的更大的的问题是是,它们们在单个个的锁上上进行同同步。这这意味着着一次只只有一个个线程可可以访问问集合,如如果有一一个线
14、程程正在读读一个 Mapp ,那那么所有有其他想想要读或或者写这这个 MMap 的线程程就必须须等待。最最常见的的 Maap 操操作, gett() 和 pput() ,可可能比表表面上要要进行更更多的处处理当遍历历一个hhashh表的bbuckket以以期找到到某一特特定的kkey时时, gget() 必必须对大大量的候候选buuckeet调用用 Obbjecct.eequaals() 。如如果keey类所所使用的的 haashCCodee() 函数不不能将vvaluue均匀匀地分布布在整个个hassh表范范围内,或或者存在在大量的的hassh冲突突,那么么某些bbuckket链链就会比比其
15、他的的链长很很多,而而遍历一一个长的的hassh链以以及对该该hassh链上上一定百百分比的的元素调调用 eequaals() 是是一件很很慢的事事情。在在上述条条件下,调用 get() 和 put() 的代价高的问题不仅仅是指访问过程的缓慢,而且,当有线程正在遍历那个hash链时,所有其他线程都被锁在外面,不能访问这个 Map 。 (哈希表表根据一一个叫做做hassh的数数字关键键字(kkey)将将对象存存储在bbuckket中中。haash vallue是是从对象象中的值值计算得得来的一一个数字字。每个个不同的的hassh vvaluue都会会创建一一个新的的bucckett。要查查找一个
16、个对象,您您只需要要计算这这个对象象的haash vallue并并搜索相相应的bbuckket就就行了。通通过快速速地找到到相应的的bucckett,就可可以减少少您需要要搜索的的对象数数量了。译译者注)get() 执执行起来来可能会会占用大大量的时时间,而而在某些些情况下下,前面面已经作作了讨论论的有条条件的线线程安全全性问题题会让这这个问题题变得还还要糟糕糕得多。 清单11 中演演示的争争用条件件常常使使得对单单个集合合的锁在在单个操操作执行行完毕之之后还必必须继续续保持一一段较长长的时间间。如果果您要在在整个迭迭代期间间都保持持对集合合的锁,那那么其他他的线程程就会在在锁外停停留很长长的
17、一段段时间,等等待解锁锁。 实例:一一个简单单的caacheeMap 在服务务器应用用中最常常见的应应用之一一就是实实现一个个 caachee。 服服务器应应用可能能需要缓缓存文件件内容、生生成的页页面、数数据库查查询的结结果、与与经过解解析的XXML文文件相关关的DOOM树,以以及许多多其他类类型的数数据。ccachhe的主主要用途途是重用用前一次次处理得得出的结结果以减减少服务务时间和和增加吞吞吐量。ccachhe工作作负载的的一个典典型的特特征就是是检索大大大多于于更新,因因此(理理想情况况下)ccachhe能够够提供非非常好的的 geet() 性能能。不过过,使用用会妨碍碍性能的的ca
18、cche还还不如完完全不用用cacche。 如果使用用 syynchhronnizeedMaap 来来实现一一个caachee,那么么您就在在您的应应用程序序中引入入了一个个潜在的的可伸缩缩性瓶颈颈。因为为一次只只有一个个线程可可以访问问 Maap ,这这些线程程包括那那些要从从 Maap 中中取出一一个值的的线程以以及那些些要将一一个新的的 (kkey, vaaluee) 对对插入到到该maap中的的线程。 减小锁粒粒度提高 HHashhMapp 的并并发性同同时还提提供线程程安全性性的一种种方法是是废除对对整个表表使用一一个锁的的方式,而而采用对对hassh表的的每个bbuckket都都使
19、用一一个锁的的方式(或或者,更更常见的的是,使使用一个个锁池,每每个锁负负责保护护几个bbuckket)。这意味着多个线程可以同时地访问一个 Map 的不同部分,而不必争用单个的集合范围的锁。这种方法能够直接提高插入、检索以及移除操作的可伸缩性。不幸的是,这种并发性是以一定的代价换来的这使得对整个集合进行操作的一些方法(例如 size() 或 isEmpty() )的实现更加困难,因为这些方法要求一次获得许多的锁,并且还存在返回不正确的结果的风险。然而,对于某些情况,例如实现cache,这样做是一个很好的折衷因为检索和插入操作比较频繁,而 size() 和 isEmpty() 操作则少得多。
20、回页首ConccurrrenttHasshMaaputill.cooncuurreent 包中的的 CooncuurreentHHashhMapp 类(也也将出现现在JDDK 11.5中中的 jjavaa.uttil.conncurrrennt 包包中)是是对 MMap 的线程程安全的的实现,比比起 ssyncchroonizzedMMap 来,它它提供了了好得多多的并发发性。多多个读操操作几乎乎总可以以并发地地执行,同同时进行行的读和和写操作作通常也也能并发发地执行行,而同同时进行行的写操操作仍然然可以不不时地并并发进行行(相关关的类也也提供了了类似的的多个读读线程的的并发性性,但是是,只允
21、允许有一一个活动动的写线线程) 。ConncurrrenntHaashMMap 被设计计用来优优化检索索操作;实际上上,成功功的 gget() 操操作完成成之后通通常根本本不会有有锁着的的资源。要要在不使使用锁的的情况下下取得线线程安全全性需要要一定的的技巧性性,并且且需要对对Javva内存存模型(JJavaa Meemorry MModeel)的的细节有有深入的的理解。 ConncurrrenntHaashMMap 实现,加加上 uutill.cooncuurreent 包的其其他部分分,已经经被研究究正确性性和线程程安全性性的并发发专家所所正视。在在下个月月的文章章中,我我们将看看看 CC
22、onccurrrenttHasshMaap 的的实现的的细节。 ConccurrrenttHasshMaap 通通过稍微微地松弛弛它对调调用者的的承诺而而获得了了更高的的并发性性。检索索操作将将可以返返回由最最近完成成的插入入操作所所插入的的值,也也可以返返回在步步调上是是并发的的插入操操作所添添加的值值(但是是决不会会返回一一个没有有意义的的结果)。由 ConcurrentHashMap.iterator() 返回的 Iterators 将每次最多返回一个元素,并且决不会抛出 ConcurrentModificationException 异常,但是可能会也可能不会反映在该迭代器被构建之后发
23、生的插入操作或者移除操作。在对集合进行迭代时,不需要表范围的锁就能提供线程安全性。在任何不依赖于锁整个表来防止更新的应用程序中,可以使用 ConcurrentHashMap 来替代 synchronizedMap 或 Hashtable 。上述改进进使得 ConncurrrenntHaashMMap 能够提提供比 Hasshtaablee 高得得多的可可伸缩性性,而且且,对于于很多类类型的公公用案例例(比如如共享的的cacche)来来说,还还不用损损失其效效率。 好了多少少?表 1对对 Haashttablle 和和 CooncuurreentHHashhMapp 的可可伸缩性性进行了了粗略的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- CopyOnWriteArrayList 提供线程安全性和已改进的可伸缩性15036 提供 线程 安全性 改进 伸缩性 15036
链接地址:https://www.taowenge.com/p-62493087.html
限制150内