当前位置: 首页 > 图灵资讯 > java面试题> 金三银四精选java面试题-HashMap 是线程安全的吗?多线程下会有什么问题?

金三银四精选java面试题-HashMap 是线程安全的吗?多线程下会有什么问题?

来源:图灵教育
时间:2023-11-30 09:45:18
 

HashMap 是线程安全的吗?多线程下会有什么问题?

HashMap 不是线程安全的,它是非同步的数据结构。在多线程环境下,使用 HashMap 可能会出现以下问题:

  • 扩容死循环:JDK 1.7 中,HashMap 使用头插法插入元素,当多个线程同时进行扩容操作时,可能会导致环形链表的形成,从而陷入死循环。为了解决这个问题,在 JDK 1.8 中采用了尾插法插入元素,保持了链表元素的顺序,避免了死循环的问题。
  • 元素丢失:当多个线程同时执行 put 操作时,如果它们计算出的索引位置相同,就会造成前一个 key 被后一个 key 覆盖的情况,从而导致元素的丢失
  • get 为 null:一个线程执行 put 操作导致扩容时,而另一个线程同时执行 get 操作,有可能会导致 get 返回 null 的情况。这是因为在扩容过程中,HashMap 的结构发生了变化,get 操作可能会在旧的结构中查找元素而导致找不到。

为了在多线程环境下使用安全的 HashMap,可以采取以下措施:

  1. 使用线程安全的替代品:使用线程安全的集合类,如 ConcurrentHashMap,它是专门设计用于多线程环境的哈希表,提供了高效的并发性能。
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(string[] args) {
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

        concurrentMap.put("A", 1);
        concurrentMap.put("B", 2);

        // 多个线程同时访问 ConcurrentHashMap 是安全的
        Runnable runnable = () -> {
            String key = "A";
            int value = concurrentMap.get(key);
            System.out.println("Thread: " + Thread.currentThread().getId() + " - Value: " + value);
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }
}
  1. 显式同步:如果必须使用普通的 HashMap,确保在访问和修改 HashMap 时进行适当的同步,使用 synchronized 关键字或其他同步机制来保护共享资源。
import java.util.HashMap;
import java.util.Map;

public class SynchronizedHashMapExample {
    public static void main(String[] args) {
        Map<String, Integer> synchronizedMap = new HashMap<>();

        synchronizedMap.put("A", 1);
        synchronizedMap.put("B", 2);

        // 多个线程使用显式同步确保线程安全
        Runnable runnable = () -> {
            synchronized (synchronizedMap) {
                String key = "A";
                int value = synchronizedMap.get(key);
                System.out.println("Thread: " + Thread.currentThread().getId() + " - Value: " + value);
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }
}
  1. 使用线程局部变量为每个线程维护一个独立的 HashMap 实例,以避免线程间竞争。
import java.util.HashMap;
import java.util.Map;

public class ThreadLocalHashMapExample {
    public static void main(String[] args) {
        // 使用线程局部变量来维护独立的 HashMap 实例
        ThreadLocal<Map<String, Integer>> threadLocalMap = ThreadLocal.withInitial(HashMap::new);

        // 创建多个线程,每个线程都有独立的 HashMap 实例
        Runnable runnable = () -> {
            Map<String, Integer> localMap = threadLocalMap.get();
            localMap.put("A", 1);
            localMap.put("B", 2);

            String key = "A";
            int value = localMap.get(key);
            System.out.println("Thread: " + Thread.currentThread().getId() + " - Value: " + value);
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }
}

总之,在多线程环境下,应谨慎使用 HashMap,并根据具体情况选择合适的线程安全机制,以确保数据的一致性和线程安全。