面對「讀極其頻繁、寫極少」的配置熱點場景,傳統 ReentrantReadWriteLock 仍有鎖開銷。本文通過基準測試對比,證明不可變物件 + 原子引用替換可消除讀端鎖,顯著提升吞吐。
// 可變配置類
class AccountConfig {
private double membershipDiscount;
private double specialEventDiscount;
// getter/setter...
}
// 讀線程
lock.readLock().lock();
AccountConfig cfg = map.get(user);
double price = orderPrice * (1 - cfg.getMembershipDiscount()) * (1 - cfg.getSpecialEventDiscount());
lock.readLock().unlock();
// 寫線程
lock.writeLock().lock();
cfg.setMembershipDiscount(newVal);
lock.writeLock().unlock();
// 不可變配置類
final class ImmutableAccountConfig {
private final double membershipDiscount;
private final double specialEventDiscount;
public ImmutableAccountConfig(double m, double s) { this.membershipDiscount = m; this.specialEventDiscount = s; }
// 僅 getter,無 setter
}
// 讀線程:零鎖!
ImmutableAccountConfig cfg = map.get(user);
double price = orderPrice * (1 - cfg.getMembershipDiscount()) * (1 - cfg.getSpecialEventDiscount());
// 寫線程:構建新物件,原子 put 替換
ImmutableAccountConfig old = map.get("user1");
ImmutableAccountConfig fresh = new ImmutableAccountConfig(newDiscount, old.getSpecialEventDiscount());
map.put("user1", fresh); // ConcurrentHashMap.put 為原子操作
ReadWriteLock: 5,289,501,171 ns (~5.29 秒) Immutable : 3,599,621,120 ns (~3.60 秒) 提升約 32%
雖未達數量級差距,但在高併發讀場景下,消除鎖爭用帶來的延遲抖動降低更具價值。
若有多個寫線程,仍需對「構建新物件 + put」加寫鎖(或用 computeIfPresent 原子更新),否則會出現更新丟失。讀線程依然無鎖。
若配置為 Map/集合,推薦 ImmutableMap/ImmutableList:
ImmutableMap<String, Double> cfg = ImmutableMap.<String, Double>builder()
.put("membership", 0.02).put("event", 0.05).build();
// 更新:複製到可變 Map,修改,再建新 ImmutableMap
Map<String, Double> tmp = Maps.newHashMap(cfg);
tmp.put("membership", 0.03);
ImmutableMap<String, Double> newCfg = ImmutableMap.copyOf(tmp);
| 場景特徵 | 推薦方案 |
|---|---|
| 讀寫比 > 1000:1,物件較小 | 不可變類 + 原子引用 |
| 寫入頻繁、物件巨大 | ReadWriteLock 或無鎖資料結構 |
| 需強一致性事務 | 資料庫/STM/分佈式鎖 |
不可變類不是銀彈,但在「讀多寫少、配置類資料」這類經典場景中,是比鎖更優雅、更高效的選擇。
原創文章,轉載請註明:https://www.cnblogs.com/Binhua-Liu/p/5573444.html