Java同步机制失效:自定义多线程HashMap为何返回不一致结果?
多线程自定义HashMap结果不一致问题
我尝试实现一个适用于多线程环境的自定义HashMap,但运行后得到了不一致的结果,怀疑是多线程同时访问put方法导致的。
问题代码
class Main{ public static void main(String[] args){ CustomMap<Integer,Integer> m = new CustomMap<>(); Thread thread1 = new Thread(new MyThread(m)); Thread thread2 = new Thread(new MyThread(m)); thread1.start(); thread2.start(); try{ thread1.join(); thread2.join(); } catch(InterruptedException e){ System.out.println("interrupted exception"); } for(int i=0;i<1000;i++){ System.out.println(i + " -> " + m.get(i)); } } } class MyThread implements Runnable{ CustomMap<Integer,Integer> m; public MyThread(CustomMap<Integer,Integer> m){ this.m = m; } public void run(){ for(int i=0;i<1000;i++){ m.put(i,m.getOrDefault(i,0)+1); } } } class CustomMap<K,V>{ private final int INITIAL_SIZE = 16; private int size; private int capacity; private Node<K,V>[] hashTable; public static class Node<K,V>{ private K key; private V value; private Node<K,V> next; Node(K key, V val){ this.key = key; this.value = val; } public V getValue(){ return this.value; } public Node getNext(){ return this.next; } public K getKey(){ return this.key; } } public CustomMap(){ hashTable = (Node<K, V>[]) new Node[INITIAL_SIZE]; this.capacity = INITIAL_SIZE; size = 0; } public int getHash(K key){ return key == null ? 0 : Math.abs(key.hashCode()) % capacity; } public synchronized void put(K key, V value){ int idx = getHash(key); if(hashTable[idx]==null){ hashTable[idx] = new Node<>(key,value); size++; } else{ Node<K,V> curr = hashTable[idx]; Node<K,V> prev = curr; while(curr !=null){ if((key == null && curr.key == null) || (key != null && key.equals(curr.key))){ curr.value = value; return; } prev = curr; curr = curr.next; } prev.next = new Node<>(key,value); size++; } } public synchronized V get(K key){ int hash = getHash(key); if(hashTable[hash]==null){ return null; } else{ Node<K,V> curr = hashTable[hash]; while(curr != null){ if((key == null && curr.key == null) || ( key!=null && key.equals(curr.key))){ return curr.value; } curr = curr.next; } return null; } } public synchronized V getOrDefault(K key, V defaultVal){ V val = get(key); return val != null ? val : defaultVal; } }
问题原因
虽然put、get、getOrDefault方法都单独加了synchronized,但**m.put(i, m.getOrDefault(i,0)+1)是两个独立的同步操作,组合起来不是原子性的**:
- 线程A执行
getOrDefault拿到值0,还没执行put时,线程B也执行getOrDefault拿到同一个键的0值 - 之后两个线程都将该键的值设为1,最终本该是2的结果变成了1,这就是竞态条件导致的结果不一致。
解决方法
要保证"获取-修改-写入"的原子性,有两种可行方案:
方案1:在CustomMap中新增原子性的递增方法
在CustomMap类中添加一个用synchronized修饰的increment方法,把获取、修改、写入逻辑放在同一个同步块里:
public synchronized void increment(K key) { // 这里假设V是Integer类型,若要通用可做适配 V current = getOrDefault(key, (V) Integer.valueOf(0)); int newValue = (Integer) current + 1; put(key, (V) Integer.valueOf(newValue)); }
然后修改MyThread的run方法,直接调用这个原子方法:
public void run(){ for(int i=0;i<1000;i++){ m.increment(i); } }
方案2:在调用方对CustomMap实例加锁
在MyThread的run方法里,对m加锁,确保每次getOrDefault和put操作是连续的原子操作:
public void run(){ for(int i=0;i<1000;i++){ synchronized(m) { m.put(i, m.getOrDefault(i,0)+1); } } }
两种方案都能避免竞态条件,保证多线程下结果的一致性。
内容的提问来源于stack exchange,提问作者A7781
相关产品推荐
相关产品推荐

