|
21 | 21 | * <li>ConcurrentMap - only when there are simultaneously readers & writers in multiple threads |
22 | 22 | * <li>HashMap via volatile - preferred for background thread updates |
23 | 23 | * <li>synchronized HashMap - when simultaneous readers & writers are uncommon (e.g. tags) |
| 24 | + * <li>FlatHashtable - lock-free reads (no lock, no volatile; benign-race) of a fixed, once-built |
| 25 | + * keyed set; a find-or-create table, not a general concurrent Map (no arbitrary put/remove) |
24 | 26 | * </ul> |
25 | 27 | * |
26 | 28 | * <p> |
@@ -104,6 +106,46 @@ static void fill(Map<String, Integer> map) { |
104 | 106 | } |
105 | 107 | } |
106 | 108 |
|
| 109 | + // FlatHashtable's contribution here is the lock-free concurrent read: get() is a plain array |
| 110 | + // probe |
| 111 | + // with no lock and no volatile — safe under concurrency because the table is published once (a |
| 112 | + // final static field) and each entry's identity fields are final. (Fixture mirrors the one in |
| 113 | + // SingleThreadedMapBenchmark; the benchmarks are self-contained.) |
| 114 | + static final class IntEntry { |
| 115 | + final String key; |
| 116 | + final int value; |
| 117 | + |
| 118 | + IntEntry(String key, int value) { |
| 119 | + this.key = key; |
| 120 | + this.value = value; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + static final class IntEntryKeyStrategy extends FlatHashtable.StringKeyStrategy<IntEntry> { |
| 125 | + static final IntEntryKeyStrategy INSTANCE = new IntEntryKeyStrategy(); |
| 126 | + |
| 127 | + private IntEntryKeyStrategy() {} |
| 128 | + |
| 129 | + @Override |
| 130 | + public boolean matches(String key, IntEntry entry) { |
| 131 | + return key.equals(entry.key); |
| 132 | + } |
| 133 | + |
| 134 | + @Override |
| 135 | + public long hashOf(IntEntry entry) { |
| 136 | + return hash(entry.key); |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + static IntEntry[] _create_flat() { |
| 141 | + // Sized to the key count (FlatHashtable is fixed-capacity, no resize): load factor <= 0.5. |
| 142 | + IntEntry[] table = FlatHashtable.create(IntEntry.class, INSERTION_KEYS.length); |
| 143 | + for (int i = 0; i < INSERTION_KEYS.length; ++i) { |
| 144 | + FlatHashtable.insert(table, new IntEntry(INSERTION_KEYS[i], i), IntEntryKeyStrategy.INSTANCE); |
| 145 | + } |
| 146 | + return table; |
| 147 | + } |
| 148 | + |
107 | 149 | static final HashMap<String, Integer> _create_hashMap() { |
108 | 150 | HashMap<String, Integer> map = new HashMap<>(); |
109 | 151 | fill(map); |
@@ -177,4 +219,17 @@ public ConcurrentSkipListMap<String, Integer> create_concSkipListMap() { |
177 | 219 | public Integer get_concSkipListMap() { |
178 | 220 | return CONC_SKIP_LIST_MAP.get(nextLookupKey()); |
179 | 221 | } |
| 222 | + |
| 223 | + @Benchmark |
| 224 | + public IntEntry[] create_flatHashtable() { |
| 225 | + return _create_flat(); |
| 226 | + } |
| 227 | + |
| 228 | + static final IntEntry[] FLAT_TABLE = _create_flat(); |
| 229 | + |
| 230 | + @Benchmark |
| 231 | + public IntEntry get_flatHashtable() { |
| 232 | + // Lock-free concurrent read of the shared, once-published table. |
| 233 | + return FlatHashtable.get(FLAT_TABLE, nextLookupKey(), IntEntryKeyStrategy.INSTANCE); |
| 234 | + } |
180 | 235 | } |
0 commit comments