首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 集合源碼剖析:Hashtable 線程安全實現與與 HashMap 差異

     編輯:Java 相關知識

Hashtable 是 JDK 1.0 遺留的線程安全哈希表,雖已被 ConcurrentHashMap 取代,但其同步機制、擴容策略、與 HashMap 差異仍具教學價值。

核心字段與初始化


private transient Entry[] table;      // 桶數組
private transient int count;          // 鍵值對數量
private int threshold;                // 擴容閾值 = capacity * loadFactor
private float loadFactor;             // 默認 0.75f
private transient int modCount = 0;   // 修改次數,支援 fail-fast

// 默認構造器:容量 11,負載因子 0.75
public Hashtable() { this(11, 0.75f); }

關鍵差異:Hashtable vs HashMap

維度HashtableHashMap (JDK8)
線程安全所有 public 方法 synchronized非線程安全
默認容量11 (質數)16 (2 的冪)
容量要求任意整數必須 2 的冪
Null Key/Value❌ 存入拋 NPE✅ Key 1個 null,Value 多個 null
哈希計算key.hashCode() & 0x7FFFFFFFhash(key) = h ^ (h >>> 16)
索引定位(hash & 0x7FFFFFFF) % length (取模)(n-1) & hash (位與)
擴容策略newCap = oldCap * 2 + 1 (質數傾向)newCap = oldCap << 1 (保持 2 的冪)
迭代器Enumeration / Iterator (fail-fast)Iterator (fail-fast)
繼承體系extends Dictionaryextends AbstractMap

同步機制分析


// 典型同步方法
public synchronized V get(Object key) { ... }
public synchronized V put(K key, V value) { ... }
public synchronized V remove(Object key) { ... }
public synchronized void putAll(Map t) { ... }

// 缺點:鎖粒度粗,整個表鎖住,高併發嚴重爭用
// 替代:Collections.synchronizedMap(new HashMap<>()) 同樣粗粒度
// 最佳:ConcurrentHashMap (分段鎖/CAS 細粒度)

核心操作源碼走讀

put(K, V)

  1. Value 不能為 null → NPE
  2. 計算 hash、索引
  3. 遍歷鏈表:Key 存在 → 替換 Value,返回舊值
  4. Key 不存在:檢查 count >= threshold → rehash() 擴容
  5. 頭插法加入新 Entry,count++,modCount++

rehash() 擴容


protected void rehash() {
    int oldCap = table.length;
    Entry[] oldMap = table;
    int newCap = oldCap * 2 + 1;  // 質數傾向,減少碰撞
    Entry[] newMap = new Entry[newCap];
    
    threshold = (int)(newCap * loadFactor);
    table = newMap;
    modCount++;
    
    // 重新散列所有節點
    for (int i = oldCap; i-- > 0; ) {
        for (Entry e = oldMap[i]; e != null; ) {
            Entry next = e.next;
            int index = (e.hash & 0x7FFFFFFF) % newCap;
            e.next = newMap[index];
            newMap[index] = e;
            e = next;
        }
    }
}

contains(Object value)

僅支援按 Value 查找,遍歷整表 O(n),Value 不能為 null。

為什麼棄用 Hashtable?

  • 性能瓶頸: 全表鎖,吞吐量隨線程數線性下降
  • API 殘缺: 無 computeIfAbsent、merge、forEach 等現代 API
  • 擴容阻塞: 單線程遷移所有節點,長鏈表下停頓明顯
  • 替代方案成熟: ConcurrentHashMap (JDK8+ CAS+同步桶)、Collections.synchronizedMap (臨時兼容)

遺留代碼遷移指南


// 遷移前
Hashtable table = new Hashtable<>();
table.put("k", "v");

// 遷移後:高併發場景
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("k", "v");
map.computeIfAbsent("k", k -> expensiveCompute());

// 遷移後:單線程/低併發,需同步
Map map = Collections.synchronizedMap(new HashMap<>());
// 復合操作需手動同步:
synchronized(map) {
    if (!map.containsKey("k")) map.put("k", compute());
}

總結

Hashtable 以粗粒度同步實現線程安全,犧牲了擴展性。理解其取模定位、質數擴容、同步鎖全表等設計,有助於理解 ConcurrentHashMap 如何通過細粒度鎖、CAS、紅黑樹化解這些問題。新代碼嚴禁使用 Hashtable。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved