HashMap Vs. ConcurrentHashMap Vs. SynchronizedMap – 如何在Java中同步HashMap
Contents
HashMap
在Java中是一种非常强大的数据结构。我们会经常使用它并且在绝大多数的应用中都会使用到它。这里有一些我之前写的例子: 如何实现线程安全的缓存 , 如何转换 HashMap 为 ArrayList?
在上面的例子中,我们都使用了 HashMap
,但这些是相当简单的 HashMap
的用法。 HashMap 是一个非同步
的集合类。
你有过以下的问题吗?
ConcurrentHashMap
和Collections.synchronizedMap(Map)
之间的区别是什么?ConcurrentHashMap
和Collections.synchronizedMap(Map)
之间的性能差别如何?ConcurrentHashMap vs Collections.synchronizedMap()
- 常见的
HashMap
和ConcurrentHashMap
的面试题
这本教程中,我们将覆盖以上所有的疑问以及为什么和如何同步 HashMap
的原因?
Why ?
Map
对象是一个存储元素的关联的容器,通过唯一标识的 key
和映射的 value
组成。如果你有一个非常高并发的应用,你可能想在不同的线程中修改或读取键值,那么理想的情况下是使用并发的 HashMap
。最典型的例子就是生产者和消费者的读写处理。
所以,线程安全的 Map
意味着什么?如果 多线程
并发地访问一个 hash map
,并且至少有一条线程修改 map
的结构,它 必须在外部被同步
,以免视图内容不一致。
How ?
有两种方式可以同步 HashMap
- Java 中的
Collections.synchronizedMap()
方法 - 使用
ConcurrentHashMap
HashMap Vs. synchronizedMap Vs. ConcurrentHashMap
//Hashtable
Map<String, String> normalMap = new Hashtable<String, String>();
//synchronizedMap
synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
concurrentHashMap = new ConcurrentHashMap<String, String>();
ConcurrentHashMap
- 当在你的项目中需要非常高并发的时候,你应该使用
ConcurrentHashMap
- 它是线程安全的,并且不需要同步整个
Map
- 读是非常快的,而写是通过锁的
- 没有在
object
级别的锁 - 锁在
HashMap
的bucket
级别具有更精细的粒度 - 如果一个线程尝试修改
ConcurrentHashMap
,而另一个线程正在迭代它,则ConcurrentHashMap
不会抛出ConcurrentModificationException
异常。 ConcurrentHashMap
使用多个锁
SynchronizedHashMap
- 在
Object
级别的锁 - 每次的 读/写 操作都要求获得锁
- 锁定整个集合是一个性能的开销
- 这本质上只允许一条线程访问整个
Map
并且会阻塞所有其他的线程 - 它可能会引起争议
SynchronizedHashMap
返回Iterator
,它会在并发修改时失败
现在让我们看一下代码
- 创建类
CrunchifyConcurrentHashMapVsSynchronizedHashMap.java
- 为每个
HashTable
、SynchronizedMap
和CrunchifyConcurrentHashMap
创建一个对象 - 从
Map
中添加和检索 500K 个条目 - 压测开始和结束时间并用毫秒显示
- 我们将使用 ExecutorService 来并行地执行5条线程
CrunchifyConcurrentHashMapVsSynchronizedMap.java
package crunchify.com.tutorials;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author Crunchify.com
*
*/
public class CrunchifyConcurrentHashMapVsSynchronizedMap {
public final static int THREAD_POOL_SIZE = 5;
public static Map<String, Integer> crunchifyHashTableObject = null;
public static Map<String, Integer> crunchifySynchronizedMapObject = null;
public static Map<String, Integer> crunchifyConcurrentHashMapObject = null;
public static void main(String[] args) throws InterruptedException {
// Test with Hashtable Object
crunchifyHashTableObject = new Hashtable<String, Integer>();
crunchifyPerformTest(crunchifyHashTableObject);
// Test with synchronizedMap Object
crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>());
crunchifyPerformTest(crunchifySynchronizedMapObject);
// Test with ConcurrentHashMap Object
crunchifyConcurrentHashMapObject = new ConcurrentHashMap<String, Integer>();
crunchifyPerformTest(crunchifyConcurrentHashMapObject);
}
public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException {
System.out.println("Test started for: " + crunchifyThreads.getClass());
long averageTime = 0;
for (int i = 0; i < 5; i++) {
long startTime = System.nanoTime();
ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (int j = 0; j < THREAD_POOL_SIZE; j++) {
crunchifyExServer.execute(new Runnable() {
@SuppressWarnings("unused")
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000);
// Retrieve value. We are not using it anywhere
Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber));
// Put value
crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber);
}
}
});
}
// Make sure executor stops
crunchifyExServer.shutdown();
// Blocks until all tasks have completed execution after a shutdown request
crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
long entTime = System.nanoTime();
long totalTime = (entTime - startTime) / 1000000L;
averageTime += totalTime;
System.out.println("2500K entried added/retrieved in " + totalTime + " ms");
}
System.out.println("For " + crunchifyThreads.getClass() + " the average time is " + averageTime / 5 + " ms\n");
}
}
结果
Test started for: class java.util.Hashtable
500K entried added/retrieved in 1432 ms
500K entried added/retrieved in 1425 ms
500K entried added/retrieved in 1373 ms
500K entried added/retrieved in 1369 ms
500K entried added/retrieved in 1438 ms
For class java.util.Hashtable the average time 1407 ms
Test started for: class java.util.Collections$SynchronizedMap
500K entried added/retrieved in 1431 ms
500K entried added/retrieved in 1460 ms
500K entried added/retrieved in 1387 ms
500K entried added/retrieved in 1456 ms
500K entried added/retrieved in 1406 ms
For class java.util.Collections$SynchronizedMap the average time 1428 ms
Test started for: class java.util.concurrent.ConcurrentHashMap
500K entried added/retrieved in 413 ms
500K entried added/retrieved in 351 ms
500K entried added/retrieved in 427 ms
500K entried added/retrieved in 337 ms
500K entried added/retrieved in 339 ms
For class java.util.concurrent.ConcurrentHashMap the average time 373 ms <== Much faster