概述
本文介绍ConcurrentHashMap的原理,并通过例子讲解如何在多线程环境下使用ConcurrentHashMap。
ConcurrentHashMap要点
- 与Hastable或synchronizedMap会锁住整个map来保证线程安全,而ConcurrentHashMap允许读线程和写线程的并发地进行操作。 也就是说,ConcurrentHashMap允许同时有一些线程修改map,其他一些线程读取map中的数据。而Hastable或synchronizedMap只允许在一个时间点只有一个线程进行操作。
- ConcurrentHashMap允许任意数量的并发读线程和有限数量的并发写入线程,并且读取和写入线程可以同时在map上进行操作。
- 读取线程(reader)执行检索操作,例如get,containsKey,size,isEmpty,并迭代映射的键集。
- 写线程(writer)执行更新操作,例如put和remove。
- ConcurrentHashMap的迭代器是弱一致的,这意味着迭代器可能不会反映自构造以来的最新更新。迭代器应该只由一个线程使用,如果在使用迭代器时修改了映射,则不会抛出ConcurrentModificationException。
- 不像阻塞容器对整个容器加锁,ConcurrentHashMap使用了比较细粒度的锁来实现,这样可以保证读者和写者同时进行操作。
ConcurrentHashMap例子
package ConcurrentCollections;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentHashMapExp {
private final ConcurrentHashMap<Integer,String> conHashMap = new ConcurrentHashMap<Integer,String>();
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(4);
ConcurrentHashMapExp ob = new ConcurrentHashMapExp();
service.execute(ob.new WriteThreasOne());
service.execute(ob.new WriteThreasTwo());
service.execute(ob.new ReadThread("read1"));
service.execute(ob.new ReadThread("read2"));
service.shutdown();
}
// producer1
class WriteThreasOne implements Runnable {
@Override
public void run() {
for (int i = 0; ; i++) {
// 若i不存在,则放入到并发map
conHashMap.putIfAbsent(i, "A" + i);
System.out.println(Thread.currentThread().getName() + "put: " + "A"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// producer2
class WriteThreasTwo implements Runnable {
@Override
public void run() {
for(int i= 1000; i<=2000; i++) {
conHashMap.put(i, "B"+ i);
System.out.println(Thread.currentThread().getName() + "put: " + "B"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// consumer1
// 通过迭代器遍历并发map的元素
// 注意:每次迭代器都是从头开始迭代。
class ReadThread implements Runnable {
private final String name;
public ReadThread(String name) {
this.name = name;
System.out.println("Start: " + this.name);
}
@Override
public void run() {
// 注意:若把迭代器构建写在这里,在循环内部将不会获取到新添加的key-valu值。
// Iterator<Integer> ite = conHashMap.keySet().iterator();
for (int j = 1; ; j++) {
Iterator<Integer> ite = conHashMap.keySet().iterator();
while (ite.hasNext()) {
Integer key = ite.next();
System.out.println(this.name + " get " + key + " : " + conHashMap.remove(key));
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
ConcurrentHashMap 与 Hashtable
- ConcurrentHashMap基于hash表实现,允许放入空元素,但Hashtable不允许。
- get(),put(),remove()等方法在Hashtable中会进行同步,而ConcurrentHashMap不使用synchronized方法进行并发同步。
- 在并发修改时,迭代访问Hashtable元素将抛出ConcurrentModificationException,而ConcurrentHashMap则不会。
ConcurrentHashMap 和 HashMap
- ConcurrentHashMap是线程安全的,而HashMap不是(若是多个线程同时写入需要进行同步)。
- ConcurrentHashMap和HashMap都是基于hash表实现。
- ConcurrentHashMap支持检索的完全并发。而HashMap需要使用Collections.synchronizedMap()进行同步。
- ConcurrentHashMap为实例化时可以更改的更新提供并发级别。
- 在并发修改中,HashMap抛出ConcurrentModificationException,而ConcurrentHashMap则不会。
ConcurrentHashMap 和 HashMap 和 Hashtable
- HashMap不是线程安全的Map,不应该被多个线程同时使用。
- Hashtable是线程安全的Map,在同一时间只允许一个线程执行read/update操作。
- SynchronizedMap基于Map实现了线程安全的封装。它由Collections.synchronizedMap(Map)工厂方法生成。synchronizedMap也只允许一次只有一个线程在map上操作。
- ConcurrentHashMap是一个线程安全的Map,具有更大的灵活性和更高的可伸缩性,因为它使用一种特殊的锁定机制,使多个线程能够同时读取/更新map。
总结
本文介绍了ConcurrentHashMap的基本原理,并通过一个实际的例子来说明它的用法。