åºæ¬çãªããã°ã©ãã³ã°ã®ååã¯æ¬¡ã®ãšããã§ããè»èŒªãåçºæããªãã§ãã ããã ããããæã ãäœãèµ·ãã£ãŠããã®ããããŒã«ãæ£ãã䜿çšããæ¹æ³ãç解ããããã«ããããè¡ãå¿ èŠããããŸãã ä»æ¥ãConcrurrentHashMapãçºæããŸããã
ãŸãã2ã€ã®ããšãå¿ èŠã§ãã 2ã€ã®ãã¹ãããå§ããŸããã-æåã®ãã¹ãã§ã¯ãå®è£ ã«ããŒã¿ç«¶åããªãããšã確èªããŸãïŒå®éãæ¢ç¥ã®äžæ£ãªå®è£ ããã¹ãããããšã§ãã¹ããæ£ãããã©ããã確èªããå¿ èŠããããŸãïŒã2çªç®ã®ãã¹ãã䜿çšããŠã¹ã«ãŒãããã®èŠ³ç¹ããããã©ãŒãã³ã¹ããã¹ãããŸãã
Mapã€ã³ã¿ãŒãã§ãŒã¹ã®ããã€ãã®ã¡ãœããã®ã¿ãæ€èšããŠãã ããã
public interface Map<K, V> { V put(K key, V value); V get(Object key); V remove(Object key); int size(); }
ã¹ã¬ããã»ãŒããªæ£åœæ§ãã¹ã
ã¹ã¬ããã»ãŒããã£ãã¹ããååã«å æ¬çã«èšè¿°ããããšã¯ã»ãšãã©äžå¯èœã§ããããã«ã JLSã®ç¬¬17ç« ã§å®çŸ©ãããŠãããã¹ãŠã®åŽé¢ãèæ ®ããå¿ èŠããããŸããããã«ããã¹ãã¯ããŒããŠã§ã¢ã¡ã¢ãªãŸãã¯JVMå®è£ ã®ã¢ãã«ã«å€§ããäŸåããŸãã
ã¹ã¬ããã»ãŒããªæ£åœæ§ãã¹ãã§ã¯ãããŒã¿ã®äžæŽåãæ€åºããããã«ã³ãŒããå®è¡ããjcstressãªã©ã®æ¢è£œã®ã¹ãã¬ã¹ãã¹ãã©ã€ãã©ãªã®1ã€ã䜿çšããŸãã jcstressã¯ãŸã å®éšçãšããŠããŒã¯ãããŠããŸãããããè¯ãéžæã§ãã ç¬èªã®äžŠååŠçãã¹ããäœæããã®ãé£ããçç±-ã·ãã¬ãã®è¬çŸ©ãåç §ããŠãã ããã
jstressãå®è¡ããã«ã¯ãjstress jcstress- gradle -pluginã䜿çšããŸãã å®å šãªãœãŒã¹ã³ãŒãã¯how-it-works-concurrent-mapã«ãããŸã ã
public class ConcurrentMapThreadSafetyTest { @State public static class MapState { final Map<String, Integer> map = new HashMap<>(3); } @JCStressTest @Description("Test race map get and put") @Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "return 0L and 1L") @Outcome(expect = FORBIDDEN, desc = "Case violating atomicity.") public static class MapPutGetTest { @Actor public void actor1(MapState state, LongResult2 result) { state.map.put("A", 0); Integer r = state.map.get("A"); result.r1 = (r == null ? -1 : r); } @Actor public void actor2(MapState state, LongResult2 result) { state.map.put("B", 1); Integer r = state.map.get("B"); result.r2 = (r == null ? -1 : r); } } @JCStressTest @Description("Test race map check size") @Outcome(id = "2", expect = ACCEPTABLE, desc = "size of map = 2 ") @Outcome(id = "1", expect = FORBIDDEN, desc = "size of map = 1 is race") @Outcome(expect = FORBIDDEN, desc = "Case violating atomicity.") public static class MapSizeTest { @Actor public void actor1(MapState state) { state.map.put("A", 0); } @Actor public void actor2(MapState state) { state.map.put("B", 0); } @Arbiter public void arbiter(MapState state, IntResult1 result) { result.r1 = state.map.size(); } } }
æåã®MapPutGetTestãã¹ãã§ã¯ãããããã¡ãœããactor1ãšactor2ãåæã«å®è¡ãã2ã€ã®ã¹ã¬ããããããŸããäž¡æ¹ãšããããã«å€ãå ¥ããŠãã§ãã¯ããã¯ããŸããããŒã¿ã®ç«¶åããªããã°ãäž¡æ¹ã®ã¹ã¬ããã¯æå®ãããå€ãèŠãã¯ãã§ãã
2çªç®ã®MapSizeTestã§ã¯ããããã«2ã€ã®ç°ãªãããŒãåæã«é 眮ãããµã€ãºã確èªããåŸ-ããŒã¿ã®ç«¶åããªãå Žå-æåŸ ãããçµæã¯2ã«ãªããŸãã
ãã¹ãã®æ£ç¢ºããæ€èšŒããã«ã¯ãæããã«ã¹ã¬ããã»ãŒããªHashMapã§ãã¹ããå®è¡ããŸããååæ§ã®éåã芳å¯ããå¿ èŠããããŸãã ã¹ã¬ããã»ãŒããªConcurrentHashMapã§ãã¹ããå®è¡ããå Žåãäžè²«æ§ã®éåã¯èŠãããŸããã
HashMapã®çµæïŒ
[FAILED] ru.skuptsov.concurrent.map.test.ConcurrentMapTest.MapPutGetTest Observed state Occurrences Expectation Interpretation -1, 1 293,867 FORBIDDEN Case violating atomic 0, -1 282,190 FORBIDDEN Case violating atomic 0, 1 28,013,763 ACCEPTABLE return 0 and 1 [FAILED] ru.skuptsov.concurrent.map.test.ConcurrentMapTest.MapSizeTest Observed state Occurrences Expectation Interpretation 1 1,434,783 FORBIDDEN size of map = 1 race 2 11,733,097 ACCEPTABLE size of map = 2
ã¹ã¬ããã»ãŒãHashMapã§ã¯ãçµ±èšçãªéã®äžè²«æ§ã®ãªãçµæãèŠãããäž¡æ¹ã®ãã¹ãã倱æããŸããã
ã¹ã¬ããã»ãŒããªConcurrentHashMapã®çµæïŒ
[OK] ru.skuptsov.concurrent.map.test.ConcurrentMapTest.MapPutGetTest Observed state Occurrences Expectation Interpretation 0, 1 20,195,000 ACCEPTABLE [OK] ru.skuptsov.concurrent.map.test.ConcurrentMapTest.MapSizeTest Observed state Occurrences Expectation Interpretation 2 6,573,730 ACCEPTABLE size of map = 2
ConcurrentHashMapã¯ãã¹ãã«åæ ŒããŸãããå°ãªããšãããã¹ãã§ããã€ãã®åçŽãªåæå®è¡ã®åé¡ãæ€åºã§ããããšãèªèã§ããŸãã Collection.synchronizedMapãšHashTableã«ã€ããŠãåãçµæã確èªã§ããŸãã
æãé©åãªConcurrentHashMapã®è©Šè¡
æåã®åçŽãªã¢ãããŒãã¯ãå éšæ§é ïŒãã±ããã®é åïŒãžã®åã¢ã¯ã»ã¹ãåçŽã«åæããããšã§ãã
å®éãéä¿¡ãããããããããã€ããŒã«å¯ŸããŠäžŠåã©ãããŒãäœæã§ããŸãã Java.util.Collections.synchronizedMapãHashtableãããã³Guava synchronizedMultimapã¯åãããšãè¡ããŸãã
public class SynchrinizedHashMap<K, V> extends BaseMap<K, V> implements Map<K, V>, IMap<K, V> { private final Map<K, V> provider; private final Object monitor; public SynchronizedHashMap(Map<K, V> provider) { this.provider = provider; monitor = this; } @Override public V put(K key, V value) { synchronized (monitor) { return provider.put(key, value); } } @Override public V get(Object key) { synchronized (monitor) { return provider.get(key); } } @Override public int size() { synchronized (monitor) { return provider.size(); } } }
ããã¥ã¡ã³ãã«ãããšãäžæ®çºæ§ããããããã€ããŒãžã®å€æŽã¯ã¹ã¬ããéã§è¡šç€ºãããŸãã
第äºã«ãåæã¡ãœãããçµäºãããšãåããªããžã§ã¯ãã«å¯Ÿããåæã¡ãœããã®åŸç¶ã®åŒã³åºããšçºçåã®é¢ä¿ãèªåçã«ç¢ºç«ããŸãã ããã«ããããªããžã§ã¯ãã®ç¶æ ã®å€æŽããã¹ãŠã®ã¹ã¬ããã«è¡šç€ºãããããšãä¿èšŒãããŸãã
ç§ãã¡ã®æãåçŽãªå®è£ ã¯äžŠåãã¹ãã«åæ ŒããŸãããäŸ¡æ Œã¯ã©ãã§ããïŒ ç°ãªãããŒãæäœããå Žåã§ããåã¡ãœããã¯äžåºŠã«1ã€ã®ã¹ã¬ããããæã€ããšãã§ããªãããããã«ãã¹ã¬ããã®è² è·ã®äžã§é«ãããã©ãŒãã³ã¹ãæåŸ ããããšã¯ã§ããŸããã ããã枬å®ããŸãããã
æ§èœè©Šéš
ããã©ãŒãã³ã¹ãã¹ãã§ã¯ã jmhã©ã€ãã©ãªã䜿çšããŸãã
@State(Scope.Thread) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(MICROSECONDS) public class ConcurrentMapBenchmark { private Map<Integer, Integer> map; @Param({"concurrenthashmap", "hashtable", "synchronizedhashmap"}) private String type; @Param({"1", "10"}) private Integer writersNum; @Param({"1", "10"}) private Integer readersNum; private final static int NUM = 1000; @Setup public void setup() { switch (type) { case "hashtable": map = new Hashtable<>(); break; case "concurrenthashmap": map = new ConcurrentHashMap<>(); break; case "synchronizedhashmap": map = new SynchronizedHashMap<>(new HashMap<>()); break; } } @Benchmark public void test(Blackhole bh) throws ExecutionException, InterruptedException { List<CompletableFuture> futures = new ArrayList<>(); for (int i = 0; i < writersNum; i++) { futures.add(CompletableFuture.runAsync(() -> { for (int j = 0; j < NUM; j++) { map.put(j, j); } })); } for (int i = 0; i < readersNum; i++) { futures.add(CompletableFuture.runAsync(() -> { for (int j = 0; j < NUM; j++) { bh.consume(map.get(j)); } })); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[1])).get(); } }
SynchronizedHashMapã®ããã©ãŒãã³ã¹ã¯java-s HashTableãšã»ãŒåçã§ãããConcurrentHashMapããã2åæªãããšã確èªããŸããã ããã©ãŒãã³ã¹ãæ¹åããŠã¿ãŸãããã
ããã¯ã¹ãã©ã€ãã³ã°ConcurrentHashMapã®è©Šè¡
æåã®æ¹åã¯ããããå šäœãžã®ã¢ã¯ã»ã¹ããããã¯ããã®ã§ã¯ãªããã¹ã¬ãããåããã±ããã«ã¢ã¯ã»ã¹ããå Žåã«ã®ã¿ã¢ã¯ã»ã¹ãåæããæ¹ããããšããèãã«åºã¥ããŠããå¯èœæ§ããããŸãïŒãã±ããã€ã³ããã¯ã¹= key.hashCodeïŒïŒïŒ array.lengthïŒã ãã®æ¹æ³ã¯ãããã¯ã¹ãã©ã€ãã³ã°ãŸãã¯ãã¡ã€ã³ã°ã¬ã€ã³åæãšåŒã°ããŸããTheArt of Multiprocessor Programmingãåç §ããŠãã ããã
ãã±ããã®é åã®å Žåãããã¯ã®é åãå¿ èŠã§ããèµ·åæã«ã¯ãããã¯ã®é åã®ãµã€ãºã¯é åã®å éšãµã€ãºãšçãããªããã°ãªããŸãããããã¯ã2ã€ã®ããã«ãé åã®1ã€ã®ãã±ãããæ åœããç¶æ³ãæãŸãããªãããéèŠã§ãã
ç°¡åã«ããããã«ãäžå€ã®ãã±ããé åãæã€ããããèããŸã-ããã¯ãåæ容éãæ¡åŒµã§ããªãããšãæå³ããŸãïŒN >> initialCapacityã®å ŽåãOïŒ1ïŒãããã倱ããšãååŸèŠçŽ ã®æ¿å ¥ãä¿èšŒãããŸãããŸããloadFactorãå¿ èŠãããŸããïŒ æ¡åŒµå¯èœãªäžŠè¡ãããã¯ãå¥ã®å€§ããªãããã¯ã§ãã
public class LockStripingArrayConcurrentHashMap<K, V> extends BaseMap<K, V> implements Map<K, V> { private final AtomicInteger count = new AtomicInteger(0); private final Node<K, V>[] buckets; private final Object[] locks; @SuppressWarnings({"rawtypes", "unchecked"}) public LockStripingArrayConcurrentHashMap(int capacity) { locks = new Object[capacity]; for (int i = 0; i < locks.length; i++) { locks[i] = new Object(); } buckets = (Node<K, V>[]) new Node[capacity]; } @Override public int size() { return count.get(); } @Override public V get(Object key) { if (key == null) throw new IllegalArgumentException(); int hash = hash(key); synchronized (getLockFor(hash)) { Node<K, V> node = buckets[getBucketIndex(hash)]; while (node != null) { if (isKeyEquals(key, hash, node)) { return node.value; } node = node.next; } return null; } } @Override public V put(K key, V value) { if (key == null || value == null) throw new IllegalArgumentException(); int hash = hash(key); synchronized (getLockFor(hash)) { int bucketIndex = getBucketIndex(hash); Node<K, V> node = buckets[bucketIndex]; if (node == null) { buckets[bucketIndex] = new Node<>(hash, key, value, null); count.incrementAndGet(); return null; } else { Node<K, V> prevNode = node; while (node != null) { if (isKeyEquals(key, hash, node)) { V prevValue = node.value; node.value = value; return prevValue; } prevNode = node; node = node.next; } prevNode.next = new Node<>(hash, key, value, null); count.incrementAndGet(); return null; } ... } } private boolean isKeyEquals(Object key, int hash, Node<K, V> node) { return node.hash == hash && node.key == key || (node.key != null && node.key.equals(key)); } private int hash(Object key) { return key.hashCode(); } private int getBucketIndex(int hash) { return hash % buckets.length; } private Object getLockFor(int hash) { return locks[hash % locks.length]; } private static class Node<K, V> { final int hash; K key; V value; Node<K, V> next; Node(int hash, K key, V value, Node<K, V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } } }
ã¯ã©ã¹ã®ãã¹ãŠã®ãã£ãŒã«ããfinalã§ããããšãéèŠã§ã-ããã¯å®å šãªå ¬éãä¿èšŒãããªããžã§ã¯ãã®æçµçãªäœæãŸã§èª°ãã¡ãœãããåŒã³åºããªãããšã§ã-ããã¯ã³ã³ã¹ãã©ã¯ã¿ã§åæåãããŠããããéèŠã§ãã
ãœãŒã¹ã³ãŒãã¯ãã¡ãã«ãããŸã ã
ãã¹ãçµæïŒ
ãã¡ã€ã³ã°ã¬ã€ã³åæã®å®è£
ã¯ãäžè¬çãªããã¯ãããåªããŠããããšãããããŸãã çµæã¯ã1ã€ã®ãªãŒããŒãš1ã€ã®ã©ã€ã¿ãŒã§ãConcurrentHashMapãšæ¯èŒããŠã»ãŒåãã§ãããã¹ã¬ããã®æ°ãå¢ãããšãç¹ã«ãªãŒããŒãå€ãå Žåã¯éãã倧ãããªããŸãã
ããã¯ãªãã®åæããã·ã¥ãããè©Šè¡
æ£çŽã«èšããšãåæã¯ã¹ã¬ãããé 次ãã¥ãŒã«å ¥ããå¥ã®ã¹ã¬ãããçµäºããã®ãåŸ ã€ããã䞊åããã°ã©ãã³ã°ã®æ¹æ³ã§ã¯ãããŸããã ãŸããã·ã¹ãã ã³ã³ããã¹ããåæããããã®è¿œå ã³ã¹ãã¯ãåŸ æ©äžã®ã¹ã¬ããã®æ°ãšãšãã«å¢å ããŸãããå¿ èŠãªã®ã¯ããããããŒã®å€ãå€æŽããããã®å°æ°ã®åœä»€ã ãã§ãã
æ°ããããã·ã¥ãããã®å®è£ ã«é¢ããããã€ãã®èŠä»¶ãå®çŸ©ããŸãããçè«çã«ã¯å®è£ ãæ¹åããã¯ãã§ãã ãŸããèŠä»¶ã¯æ¬¡ã®ãšããã§ãã
- ç°ãªãããŒïŒæžã蟌ã¿ãŸãã¯èªã¿åãïŒã§åäœãã2ã€ã®ã¹ã¬ãããããå Žåããããã®éã®åæã¯å¿ èŠãããŸããïŒJavaã§ã¯ã¯ãŒããã£ã¢ãªã³ã°ã¯èš±å¯ãããŸãã-é åã®2ã€ã®ç°ãªããã£ãŒã«ããžã®ã¢ã¯ã»ã¹ã¯ã¹ã¬ããã»ãŒãã§ãïŒ
- è€æ°ã®ã¹ã¬ãããåãããŒïŒæžã蟌ã¿ããã³èªã¿åãïŒã§åäœããŠããå Žåãæäœã䞊ã¹æ¿ããå¿ èŠã¯ãããŸããïŒ ææ°ã®ãã£ãã·ã¥ã®æ§é ã®åé¡ã®åå ã«ã€ã㊠ïŒãã¹ã¬ããéã®æã åã®ä¿èšŒãå¿ èŠã§ãããããŒã ããããèªã¿åãã¹ããªãŒã ããããã¯ããŠãæžã蟌ã¿ã¹ããªãŒã ãå®äºããã®ãåŸ ã¡ããã¯ãããŸããã
- æžã蟌ã¿ã¹ããªãŒã ã1ã€ããªãå Žåãè€æ°ã®ãªãŒããŒã1ã€ã®ããŒã§ãããã¯ããããšã¯æãŸãããããŸããã
ãã€ã³ã2ãš3ã«éäžããŸããããå®éãïŒ1ïŒãã±ããã®æ®çºæ§èªã¿åãé åãäœæããïŒ2ïŒæ¬¡ã®ããŒãã®æ®çºæ§èªã¿åãã§ãã±ããå ã«ç§»åã§ããå Žåããããèªã¿åãæäœãå®å šã«ããã¯ããªãŒã«ããããšãã§ããŸããããŒãèªäœã®ç®çã®æ®çºæ§ã®èªã¿åãå€ãèŠã€ãããŸã§ãªã³ã¯ãªã¹ãã
ïŒ2ïŒã®å Žåã次ã®ãã£ãŒã«ããšå€ãã£ãŒã«ããNodeã§volatileãšããŠããŒã¯ããã ãã§ãã
ïŒ1ïŒæ®çºæ§é åã®ãããªãã®ã¯ãããŸãããé åãæ®çºæ§ãšããŠå®£èšãããŠããŠããããã¯èŠçŽ ã®èªã¿åããŸãã¯æžã蟌ã¿æã«æ®çºæ§ã®ã»ãã³ãã£ã¯ã¹ãæäŸããŸããããé åã®kçªç®ã®èŠçŽ ã«ã¢ã¯ã»ã¹ããã«ã¯å€éšåæãå¿ èŠã§ãæ®çºæ§ã¯ããèªäœã§ãé ååç §ã ãã®ç®çã§AtomicReferenceArrayã䜿çšã§ããŸãããObject []é åã®ã¿ãåãå ¥ããŸãã å¥ã®æ¹æ³ãšããŠãæ®çºæ§é åã®èªã¿åããšããã¯ããªãŒã®æžã蟌ã¿ã«Unsafeã䜿çšããããšãæ€èšããŠãã ããã åãã¡ãœãããAtomicReferenceArrayãšConcurrentHashMapã§äœ¿çšãããŸãã
@SuppressWarnings("unchecked") // read array value by index private <K, V> Node<K, V> volatileGetNode(int i) { return (Node<K, V>) U.getObjectVolatile(buckets, ((long) i << ASHIFT) + ABASE); } // cas set array value by index private <K, V> boolean compareAndSwapNode(int i, Node<K, V> expectedNode, Node<K, V> setNode) { return U.compareAndSwapObject(buckets, ((long) i << ASHIFT) + ABASE, expectedNode, setNode); } private static final sun.misc.Unsafe U; // Node[] header shift private static final long ABASE; // Node.class size shift private static final int ASHIFT; static { try { // get unsafe by reflection - it is illegal to use not in java lib Constructor<Unsafe> unsafeConstructor = Unsafe.class.getDeclaredConstructor(); unsafeConstructor.setAccessible(true); U = unsafeConstructor.newInstance(); } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } Class<?> ak = Node[].class; ABASE = U.arrayBaseOffset(ak); int scale = U.arrayIndexScale(ak); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); }
volatile getNodeã§ã¯ãããã¯ãªãã§å®å šã«å€ãèªã¿åãããšãã§ããŸãã
ããã¯ããªãŒã®V getïŒãªããžã§ã¯ãããŒïŒãæžããŸãããïŒ
public V get(Object key) { if (key == null) throw new IllegalArgumentException(); int hash = hash(key); Node<K, V> node; // volatile read of bucket head at hash index if ((node = volatileGetNode(getBucketIndex(hash))) != null) { // check first node if (isKeyEquals(key, hash, node)) { return node.value; } // walk through the rest to find target node while ((node = node.next) != null) { if (isKeyEquals(key, hash, node)) return node.value; } } return null; }
æåã®è©Šã¿ã¯ãããã¯ããŒã«ã䜿çšãã倧ããªã¡ã¢ãªãªãŒããŒãããã§ãããå®éãè¿œå ã®ã¡ã¢ãªãªãã§åãããã®çŽ°ããã¢ãããŒãã䜿çšã§ããŸãããã±ããå ã®æåã®ããŒããååšããå Žåã¯ãããã¯ããã ãã§ãã ååšããªãå Žå-ååšããªãèŠçŽ ã§ãããã¯ããããšã¯ã§ãããããããŒããŒããèšå®ããããã®ããã¯ããªãŒã¡ãœãããå¿ èŠã§ã-æ¢ã«ãã®ã¡ãœãããèšè¿°ããŠããŸã-compareAndSwapNodeã¡ãœããã
@Override public V put(K key, V value) { if (key == null || value == null) throw new IllegalArgumentException(); int hash = hash(key); // no resize in this implementation - so the index will not change int bucketIndex = getBucketIndex(hash); // cas loop trying not to miss while (true) { Node<K, V> node; // if bucket is empty try to set new head with cas if ((node = volatileGetNode(bucketIndex)) == null) { if (compareAndSwapNode(bucketIndex, null, new Node<>(hash, key, value, null))) { // if we succeed to set head - then break and return null count.increment(); break; } } else { // head is not null - try to find place to insert or update under lock synchronized (node) { // check if node have not been changed since we got it // otherwise let's go to another loop iteration if (volatileGetNode(bucketIndex) == node) { V prevValue = null; Node<K, V> n = node; while (true) { ... simply walk through list under lock and update or insert value... } return prevValue; } } } } return null; }
å®å šãªãœãŒã¹ã³ãŒãã¯ãã¡ã ã
ãã®ããã©ãŒãã³ã¹ããã¹ãããŸãããïŒ
å Žåã«ãã£ãŠã¯ãConcurrentHashMapãããåªããŠããŸãããããã¯å®å šã«æ£çŽãªæ¯èŒã§ã¯ãããŸããã ConcurrentHashMapã¯èµ·åæã«ããŒãã«ã®é 延åæåãè¡ããå¢çèŠçŽ threshold = initialCapacity * loadFactorã§å°ãªããšã1åãµã€ãºãå€æŽããããã§ãã åæåãããèŠçŽ initialCapacityã䜿çšããŠãã¹ããå床å®è¡ãããšã = NïŒ= N / 6ïŒãçµæã¯ãããã«ç°ãªããŸãïŒ
ããã¯ãConcurrentHashMapã§ãã±ããé åã®åæãµã€ãºãå¢å ãããã±ããå ã®ãªã³ã¯ãªã¹ãã®é·ããæžå°ãããããããŒã«ããèŠçŽ ã®ååŸã«è²»ããããæéãçããªãããã«çºçããŸããã
ConcurrentHashMapã®ããã«ãå®å šãªéãããã¯ããŒã¿æ§é ãååŸããŠããªãããšã«æ³šæããå¿ èŠããããŸãããå¿ èŠãªã®ã¯ããã¯ã®ãªããªã³ã¯ãªã¹ãã ãã§ãããããŒã¿ã®ãµã€ãºå€æŽãšå€æŽãåæã«è¡ãããšã§ããã®ã¿ã¹ã¯ã¯ããã»ã©åçŽã§ã¯ãããŸãã- ãããèªãã§ãã ãã ã
ãªãªãžãã«ã®Java 8 ConcurrentHashMapã«ã¯ãèšåããŠããªãå€ãã®æ¹åç¹ããããŸããäŸãã°ïŒ
- åããŠäœ¿çšããåã®ã¡ã¢ãªãããããªã³ããæå°åãããã±ããããŒãã«ã®é 延åæå
- åæãµã€ãºå€æŽãã±ããé å
- LongAdderã䜿çšããŠèŠçŽ ãã«ãŠã³ãããŸãã
- ç¹æ®ãªã¿ã€ãã®ããŒãïŒ1.8以éïŒ-TreeBinsããã±ããå ã®ãªã¹ããTREEIFY_THRESHOLD = 8ããé·ããªãå Žå-ãã±ããã¯ãããŒã«ããææªã®æ€çŽ¢ã§ãã©ã³ã¹ããªãŒã«ãªããŸãïŒOïŒlogïŒNbucket_sizeïŒïŒïŒ
Java 1.8ã§ã®ConcurrentHashMapã®å®è£ ã1.7ããå€§å¹ ã«å€æŽãããããšã«æ³šæããŠãã ããã 1.7ã§ã¯ãã»ã°ã¡ã³ãã®æ°ã䞊åæ§ã®ã¬ãã«ã«çããã»ã°ã¡ã³ãã®ã¢ã€ãã¢ã§ããã Java 8ã§ã¯ããã±ããé åã¯åäžã®é åã§ãã