You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:05:24