1、HashMap线程不安全
HashMap在内部实现上采用了数组+链表/红黑树的方式来存储数据。在多线程的情况下,如果两个线程同时对同一个桶进行操作(如添加元素),就有可能会导致链表的链断或者元素被覆盖的问题,最终导致数据的不一致性。这是因为HashMap在没有同步措施的情况下,不保证线程安全,即多个线程同时访问同一个桶的时候,可能会出现竞争条件,从而导致数据不一致。
具体来说,当两个线程同时向同一个桶中添加元素时,可能会出现以下情况:
- 线程A读取桶中的元素,此时桶中只有一个元素,即key1-value1。
- 线程B也读取桶中的元素,此时桶中还是只有一个元素。
- 线程A向桶中添加一个新元素key2-value2,这时候key2与key1的hashCode()可能相同,导致两个元素在同一个桶中。
- 线程B也向桶中添加一个新元素key3-value3,同样也可能会被放在同一个桶中。
- 当线程A和线程B都执行完毕后,桶中可能会出现以下两种情况:
- key1-value1和key2-value2都在同一个链表中,而key3-value3没有被添加到桶中,导致数据丢失。
- key1-value1和key3-value3都在同一个链表中,而key2-value2没有被添加到桶中,导致数据丢失。
因此,为了避免这种情况的发生,需要使用线程安全的Map实现,如ConcurrentHashMap,或者在使用HashMap时使用同步措施,如使用synchronized关键字或者使用并发包中的锁来保证线程安全。
2、ConcurrentHashMap线程安全
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它提供了高效的并发访问和修改操作。它的线程安全性主要基于以下几个方面:
- 分段锁机制
ConcurrentHashMap内部采用了分段锁的机制,也就是将整个哈希表分成多个小的哈希表段,每个段都可以独立地进行加锁和解锁操作。这样就可以同时进行多个线程的读写操作,而不必等待其他线程释放锁,从而提高了并发性能。
- volatile修饰的元素数组
ConcurrentHashMap内部使用了一个volatile修饰的元素数组,它可以保证多个线程对数组元素的读写操作的可见性,从而保证了多线程环境下数据的一致性和安全性。
- 原子性操作
ConcurrentHashMap内部使用了一些原子性操作,比如CAS操作和volatile修饰的变量等,来保证对哈希表中的元素进行原子性的读写操作,从而避免了多线程环境下的数据竞争和不一致性问题。
3、 代码实际证明
3.1 HashMap
public class HashMapDemo implements Runnable {
private static final HashMap<String, Integer> map = new HashMap<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
map.put("key" + i + new Random(10), i);
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new HashMapDemo();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("HashMap size: " + map.size());
}
}
在上面的代码中,我们创建了一个包含10000个元素的HashMap,并启动了两个线程同时向其中添加元素。由于HashMap没有进行同步措施,因此在运行过程中可能会出现数据不一致的情况。当运行结束后,我们输出HashMap的大小,如果数据没有出现不一致性的问题,那么HashMap的大小应该为20000。但是,由于HashMap不是线程安全的,因此在某些情况下,运行结果可能会小于20000,说明出现了数据不一致性的问题。
3.2 ConcurrentHashMap
public class ConcurrentHashMapDemo implements Runnable {
private static final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
map.put("key" + i + new Random(10), i);
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new ConcurrentHashMapDemo();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("ConcurrentHashMap size: " + map.size());
}
}
在上面的代码中,我们使用了ConcurrentHashMap代替了HashMap,并且两个线程同时向其中添加元素。由于ConcurrentHashMap是线程安全的,因此在运行过程中不会出现数据不一致性的问题。当运行结束后,我们输出ConcurrentHashMap的大小,应该为20000。
这就是用代码直接证明 ConcurrentHashMap 和 HashMap的线程安全问题