concurrenthashmap.clear() 通过volatile/cas批量清空槽位而非加锁,本质是无锁、弱一致的“换表”操作;它不阻塞并发读写,旧表由gc回收,isempty()才是准确判空方式。

ConcurrentHashMap.clear() 并不逐个节点释放锁,也不需要“逐个槽位解锁”。它的实现逻辑与直觉相反:它不依赖遍历加锁再清空,而是通过批量替换整个哈希表引用 + 保证可见性来完成清空,本质是无锁、高效、原子性弱一致的操作。
下面分几个关键点讲清楚:
clear 方法的核心机制是“换表”,不是“删节点”
-
clear()不会遍历每个Node、不调用remove、不逐个synchronized槽位。 - 它直接将内部的
table数组引用置为一个全新的空数组(长度仍为原容量,但所有槽位为null)。 - 原来的旧
table会被 GC 自动回收(前提是无其他强引用)。 - 这个赋值操作本身是volatile 写(JDK 8+ 中
table字段为volatile),确保其他线程能立即看到新表。
public void clear() {
long delta = 0L; // 用于调整 baseCount(计数器)
int i = 0;
Node<k>[] tab = table;
if (tab != null && tab.length > 0) {
for (; i <blockquote><p>注意:JDK 8 的 <code>clear()</code> 实际上<strong>不是简单赋新数组</strong>,而是对每个槽位调用 <code>setTabAt(tab, i, null)</code> —— 即用 <code>Unsafe.putObjectVolatile</code> 或 CAS 清空每个位置。这是为了<strong>避免因扩容未完成导致的旧表残留问题</strong>,同时兼容正在并发 transfer 的场景。</p></blockquote>
<h3>为什么不需要“逐个释放锁”?</h3>
<ul>
<li>ConcurrentHashMap <strong>没有传统意义上的“槽位锁对象”长期持有</strong>。</li>
<li>写操作(如 <code>put</code>/<code>remove</code>)只在修改某个 bin 时临时获取该 bin 头节点的 <code>synchronized</code> 锁(JDK 8+),或使用 <code>CAS + synchronized</code> 组合,锁的作用域极小、瞬时。</li>
<li>
<code>clear()</code> 本身<strong>不获取任何锁</strong>,它只是把每个槽位设为 <code>null</code>。即使某线程正在 <code>put</code> 到某个 bin,只要 <code>clear</code> 先完成对该 bin 的 <code>setTabAt(null)</code>,后续 <code>put</code> 会发现头节点为 <code>null</code>,走初始化路径重新建链 —— 这是设计允许的竞态,不影响线性一致性(最终状态为空)。</li>
</ul>
<h3>槽位清空过程中的并发安全怎么保障?</h3>
<ul>
<li>使用 <code>setTabAt(tab, i, null)</code>,底层是 <code>Unsafe.putObjectVolatile</code> 或 <code>U.compareAndSetObject</code>,保证写操作对所有线程<strong>立即可见</strong>。</li>
<li>如果某个 bin 正在被 transfer(扩容),其头节点可能是 <code>ForwardingNode</code>;<code>clear</code> 依然会把它设为 <code>null</code>,这没问题 —— <code>ForwardingNode</code> 本身只是中转标记,清掉不影响正确性。</li>
<li>
<code>clear</code> 过程中若有线程正在 <code>get</code>,读到 <code>null</code> 就返回 <code>null</code>;若刚清完某槽位,<code>get</code> 可能读到旧值(取决于内存模型和执行时序),但这是<strong>弱一致性保证</strong>,符合 <code>ConcurrentHashMap</code> 设计目标(不要求实时强一致)。</li>
</ul>
<h3>清空后 size() 为什么可能不准?如何修正?</h3>
<ul>
<li><p><code>clear()</code> 会尝试重置计数器(<code>baseCount</code> 和 <code>CounterCell[]</code>),但多线程下仍可能存在微小误差。</p></li>
<li><p><code>size()</code> 是估算值(基于 <code>sumCount()</code>),清空后若仍有未合并的 <code>CounterCell</code>,<code>size()</code> 可能短暂大于 0。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6235" title="Java Maven Code Review"><img
src="https://img.php.cn/upload/skill/000/000/081/179084711841712.jpg" alt="Java Maven Code Review" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6235" title="Java Maven Code Review" class="overflowclass">Java Maven Code Review</a>
<p class="overflowclass">审查Java Maven项目(ZIP压缩包或GitLab仓库URL),检查代码规范、命名、模块边界、可维护性问题以及重复代码。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6235" title="Java Maven Code Review" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div></li>
<li><p>如需精确判断是否为空,应使用 <code>isEmpty()</code> —— 它只检查 <code>baseCount == 0 && counterCells == null</code>,更轻量且足够可靠。</p></li>
<li><p>不建议依赖 <code>size() == 0</code> 判断清空完成;</p></li>
<li><p><code>isEmpty()</code> 是唯一语义明确的判空方式。</p></li>
</ul>
<h3>总结关键事实</h3>
<ul>
<li>
<code>clear()</code> 是<strong>无锁批量清空</strong>,靠 volatile/CAS 写每个槽位,不是“逐个加锁再删”;</li>
<li>它不等待任何正在进行的读写操作,也不阻塞并发 <code>put</code>/<code>get</code>;</li>
<li>清空后旧数据由 GC 回收,无需手动释放锁或资源;</li>
<li>行为是<strong>弱一致性</strong>:<code>get</code> 可能短暂读到旧值,<code>size()</code> 可能延迟归零,但 <code>isEmpty()</code> 立即准确;</li>
<li>它的设计目标是<strong>高吞吐、低延迟、不牺牲并发性</strong>,而非强同步语义。</li>
</ul>
<p>不复杂但容易忽略:<code>clear()</code> 的“干净”来自内存可见性控制,而不是锁管理。</p></k>Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










