ConcurrentHashmap使用注意事项 分类: java ...

ConcurrentHashmap是jdk1.5之后引入的并发工具集合类,可以将其看作并发效率更高的map,用来替代Hashtable和synchronizedMap,但是ConcurrentHashmap只能保证自身数据在多线程的环境下不被破坏,而并不能保证业务逻辑的正确性。


举个例子:资源池中经常需要将某个资源放到一个map中缓存起来,等到需要的时候再从map中获取,代码如下:

code1:
private ConcurrentMap records = new ConcurrentHashMap();

private Record getOrCreate(String id) {
    Record rec = records.get(id); //1
    if (rec == null) {
        // record does not yet exist
        Record newRec = new Record(id);
        rec = records.putIfAbsent(id, newRec);
        if (rec == null) {
            // put succeeded, use new value
            rec = newRec;
        }
    }
    return rec;
}

concurrenthashmap实现了concurrentmap接口:

public interface ConcurrentMap<K, V> extends Map<K, V> {
    V putIfAbsent(K key, V value);
    boolean remove(Object key, Object value);
    boolean replace(K key, V oldValue, V newValue);
    V replace(K key, V value);
}

其中特别介绍下putIfAbsent(K key,V value)方法:

将指定key与value关联起来,并且返回和指定key关联的老的value值,如果以前没有value和key关联,返回null

可以看到putIfAbsent方法的作用相当于以下代码:

 if (!map.containsKey(key)) 
      return map.put(key, value);
  else
       return map.get(key);

唯一区别就是putIfAbsent是原子操作
在code1这个例子中虽然使用了Concurrenthashmap,但是也有问题,问题出在1处,多个线程可能会创建多个newRec,可能会引发后续错误。
要避免这种资源的多次创建,可以使用double checked的方式实现,在资源池场景可以使用Double checked保证我们资源仅被创建一次 ,同时尽量降低同步的代价:

private ConcurrentMap records = new ConcurrentHashMap();

private Record getOrCreate(String id) {
    Record rec = records.get(id);
    if (rec == null) {
        synchronized (this) {
            rec = records.get(id);
            if (rec == null) {
                // record does not yet exist
                Record newRec = new Record(id);
                rec = records.putIfAbsent(id, newRec);
                if (rec == null) {
                    // put succeeded, use new value
                   rec = newRec;
                }
            }
        }
    }
    return rec;
}

Concurrenthashmap支持在运行时修改修改集合,而不抛出ConcurrentModificationException异常。举一个例子:

package com.journaldev.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {

    public static void main(String[] args) {

        //ConcurrentHashMap
        Map myMap = new ConcurrentHashMap();
        myMap.put("1", "1");
        myMap.put("2", "1");
        myMap.put("3", "1");
        myMap.put("4", "1");
        myMap.put("5", "1");
        myMap.put("6", "1");
        System.out.println("ConcurrentHashMap before iterator: "+myMap);
        Iterator it = myMap.keySet().iterator();

        while(it.hasNext()){
            String key = it.next();
            if(key.equals("3")) myMap.put(key+"new", "new3");
        }
        System.out.println("ConcurrentHashMap after iterator: "+myMap);

        //HashMap
        myMap = new HashMap();
        myMap.put("1", "1");
        myMap.put("2", "1");
        myMap.put("3", "1");
        myMap.put("4", "1");
        myMap.put("5", "1");
        myMap.put("6", "1");
        System.out.println("HashMap before iterator: "+myMap);
        Iterator it1 = myMap.keySet().iterator();

        while(it1.hasNext()){
            String key = it1.next();
            if(key.equals("3")) myMap.put(key+"new", "new3");
        }
        System.out.println("HashMap after iterator: "+myMap);
    }

}

运行结果如下:

ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
    at java.util.HashMap$KeyIterator.next(HashMap.java:828)
    at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)

查看输出,很明显ConcurrentHashMap可以支持向map中添加新元素,而HashMap则抛出了ConcurrentModificationException。事实上,集合对象的迭代器提供快速失败(Fail-Fast)的机制,即修改集合对象结构或者元素数量都会使迭代器触发这个异常。


Concurrenthashmap的简要总结

1. 在读取数据的时候不需要加锁,因为public V get(Object key)方法不涉及到锁
2. put、remove方法涉及到锁,但是也并一定有锁争用,因为Concurrenthashmap是分段加锁的,如果要获取的对象正好不在同一个段(segment)上,也就不会存在争夺锁的情况,Concurrenthashmap默认有16个段
3. ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化

版权声明:本文为博主原创文章,不过你想转载就转载。

转载于:https://www.cnblogs.com/lanchun/p/4725220.html

你可能感兴趣的:(java)