我最近参加了面试,面试官向我询问了有关HashMap
和ConcurrentHashMap
的问题。在最初的线程安全性好处之后,我说它在遍历ConcurrentModificationException
时不会抛出HashMap
而不是HashMap
。
面试官继续问我为什么不抛弃它。我说内部modCount
有一个变量Iterator
,它保留了对地图进行修改的次数,而ConcurrentHashMap
正在将这个数字与它在创建时初始化的数字进行比较。如果此数字不同,则会抛出异常,ConcurrentHashMap
不会发生此比较。
然后他说 <system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
会在遍历时复制自己。
我对此声明有疑问,因为以前没有遇到过这个问题。即使阅读文档也没有给我足够的答案。
它会在任何时候制作副本(读或写)吗?
答案 0 :(得分:2)
不,它与制作任何东西的副本无关。并且抛出ConcurrentModificationException
不一定不在多线程环境下。
关于你的问题有几点。
ConcurrentModificationException
与并发无关(实际上它的名字不好)。当您迭代某些数据结构并在对结构进行一些修改后再次使用迭代器时抛出它。这称为fail fast
行为。由于这种行为,它可以在多线程上下文中抛出,但根本不依赖于它。
ConcurrentHashMap
是一个精致的结构,我建议你仔细阅读。
任何访问地图中不同存储桶的线程都是自然分开的,因此除了重新散列外,不需要在它们之间进行同步。
对于访问同一个存储桶的线程,如果存储桶为空,则使用CAS,即AtomicReference
来访问存储桶中唯一的元素。
如果正在运行的存储桶具有哈希冲突并且具有多个元素,则将其保留为链接列表(JDK 1.8的情况除外,它将链接列表重新编译为二叉树以在极端哈希下实现更好的性能碰撞)。链表的好处是,只要你以CAS方式操作下一个指针,它就会自然地支持并发。
ConcurrentHashMap
中仍然有很多编码艺术,所以我建议你仔细阅读它的源代码。