Java中的ConcurrentHashMap

时间:2020-02-23 14:34:45  来源:igfitidea点击:

ConcurrentHashMap在Java 5中引入了其他并发util,例如CountDownLatch,CyclicBarrier和BlockingQueue。

Java中的ConcurrentHashMap与Hashtable非常相似,但它提供了更好的并发级别。

我们可能知道,我们可以使用集合同步HashMap。

SynchronizedMap(Map)。
因此,ConcurrentHashMap和Collections.synchronizedmap(map)之间有什么区别在Collections.synchronizedmap(map),它锁定整个Hashtable对象,但在ConcurrentHashMap中,它只锁定其中的一部分。
你会在以后的一部分理解它。

另一个区别是,如果我们尝试在迭代时修改ConcurrentHashMap,则ConcurrentHashMap不会抛出并发映射异常。

让我们来一个非常简单的例子。
我有一个国家程序,我们将使用国家类对象作为键及其首都名称(String)作为价值。
下面的示例将了解这些键值对如何存储在ConcurrentHashMap中。

Java中的ConcurrentHashMap示例:

1.国家.java.

package org.arpit.theitroad;
public class Country {
 
 String name;
 long population;
 
 public Country(String name, long population) {
  super();
  this.name = name;
  this.population = population;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public long getPopulation() {
  return population;
 }
 public void setPopulation(long population) {
  this.population = population;
 }
@Override
public int hashCode() {
 final int prime = 31;
 int result = 1;
 result = prime * result + ((name == null) ? 0 : name.hashCode());
 return result;
}
@Override
 public boolean equals(Object obj) {
  
  Country other = (Country) obj;
   if (name.equalsIgnoreCase((other.name)))
   return true;
  return false;
 }
  
}
  1. concurrenthashmapstructure.java(主类)
import java.util.HashMap;
import java.util.Iterator;
  
public class ConcurrentHashMapStructure {
  
    /**
     * @author Arpit Mandliya
     */
    public static void main(String[] args) {
          
        Country San Franceco=new Country("San Franceco",1000);
        Country japan=new Country("Japan",10000);
          
        Country france=new Country("France",2000);
        Country russia=new Country("Russia",20000);
          
        ConcurrentHashMap<country,String> countryCapitalMap=new ConcurrentHashMap<country,String>();
        countryCapitalMap.put(San Franceco,"Delhi");
        countryCapitalMap.put(japan,"Tokyo");
        countryCapitalMap.put(france,"Paris");
        countryCapitalMap.put(russia,"Moscow");
          
        Iterator countryCapitalIter=countryCapitalMap.keySet().iterator();//put debug point at this line
        while(countryCapitalIter.hasNext())
        {
            Country countryObj=countryCapitalIter.next();
            String capital=countryCapitalMap.get(countryObj);
            System.out.println(countryObj.getName()+"----"+capital);
            }
        }
  
  
}

现在将调试点放在第23行,并右键单击"项目 - >调试" - > Java应用程序。
程序将在第23行停止执行,然后右键单击CountryCapitalMap,然后选择Watch.You将能够看到如下结构。

现在从上图中,我们可以观察以下要点

  • 有一个被称为段的段[]数组,其具有大小16.
  • 它有两个称为semmentshift和segmentMask的变量。
  • 此部分存储段类对象。 concurrenthashmap类有一个名为段的内部类
/**
     * Segments are specialized versions of hash tables.  This
     * subclasses from ReentrantLock opportunistically, just to
     * simplify some locking and avoid separate construction.
     */
static final class Segment<K,V> extends ReentrantLock implements Serializable {
 /**
      * The per-segment table.
     */
        transient volatile HashEntry<K,V>[] table;
//other methods and variables
}

现在让我们展开索引3存在的段对象。

在上图中,我们可以看到每个段类包含逻辑上的hashmap。
这里的大小:2k> =(容量/段数)它存储一个名为hashentry的类中的键值对,它类似于HashMap中的条目类。

static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;
}

当我们说,ConcurrentHashMap只锁的一部分。
它实际上锁定了一段。
因此,如果两个线程在同一康复冲击图中编写不同的段,则它允许在没有任何冲突的情况下写入操作。

所以段仅用于写入操作。
在读取操作的情况下,它允许完整的并发性,并使用volatile变量提供最近更新的值。
现在,当我们了解ConcurrentHashMap的内部结构时,我们将更容易理解PUT功能。

ConcurrencyLevel的概念:

在创建ConcurrentHashMap对象时,我们可以在构造函数中传递ConcurrencyLevel。
ConcurrencyLevel定义"估计要写入ConcurrentHashMap的线程数"。
默认ConcurrencyLevel是16.这就是为什么,我们在上面创建了16个段对象ConcururentHashMap。
实际的段数将等于ConcurrencyLevel中定义的下一个功率。
例如:假设我们已定义了ConcurrendyLevel为5,因此8段对象将创建为8 = 2 ^ 3所以3较高位的键将用于查找段的索引另一个示例:我们想要10个线程应该能够要同时访问ConcurrentHashMap,因此我们将ConcurrencyLevel定义为10,因此将创建16个段为16 = 2 ^ 4所以4较高的键将用于查找段的索引

put:

put入口的代码如下:

/**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * The value can be retrieved by calling the <tt>get</tt> method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>
     * @throws NullPointerException if the specified key or value is null
     */
    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }
 
 /**
     * Returns the segment that should be used for key with given hash
     * @param hash the hash code for the key
     * @return the segment
     */
    final Segment segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }
 //Put method in Segment:
 V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();
            try {
                int c = count;
                if (c++ > threshold) //ensure capacity
                    rehash();
                HashEntry[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry first = tab[index];
                HashEntry e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;
 
                V oldValue;
                if (e != null) {
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;
                }
                else {
                    oldValue = null;
                    ++modCount;
                    tab[index] = new HashEntry(key, hash, first, value);
                    count = c; //write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

当我们将任何键值对添加到ConcurrentHashMap时,请执行以下步骤:

  • 在ConcurrentHashMap中,密钥不能为null。键的Hashcode方法用于计算哈希码
  • Key的Hashcode方法写入不佳,因此Java开发人员添加了一个方法哈希(类似于HashMap),应用了另一个散列()函数并计算哈希码。
  • 现在我们需要首先找到段的索引,用于查找给定密钥的段,使用上面的segcer for方法。
  • 获取段后,我们使用段的put方法。当将密钥值对放入段中,它获取锁定,因此没有其他线程可以在此块中输入,然后使用哈希&(tab.length-1)找到偏出索引数组中的索引。
  • 如果我们密切关注,则段的Put方法类似于HashMap的Put方法。

putifabsent:

我们只想在kecurrenthashmap中放置元素,只有在返回旧值的情况下它没有键。
这可以写作:

if (map.get(key)==null)
 
    return map.put(key, value);
else
   return map.get(key);

如果我们使用Purifabsent方法,上面的操作是原子的。
这可能是必需的行为。
让我们在一个例子的帮助下了解:

  • 线程1在ConcurrentHashMap中放置值。
  • 同时,线程2正在尝试从ConcurrentHashMap读取(获取)值,并且它可能会返回null,因此它可能会覆盖在Concurrenthashmap中放置的任何线程1.

可能不需要上述行为,因此ConcurrentHashMap具有Purifabsent方法。

/*
 
     * {@inheritDoc}
     *
 
     * @return the previous value associated with the specified key,
 
     *         or <tt>null</tt> if there was no mapping for the key
 
     * @throws NullPointerException if the specified key or value is null
     */
 
    public V putIfAbsent(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, true);
 
    }

get:

/**
 
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     *
More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}.  (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).get(key, hash);
}
 
/* Specialized implementations of map methods */
 
//get method in Segment:
V get(Object key, int hash) {
if (count != 0) { //read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); //recheck
}
e = e.next;
}
}
return null;
}

从concurrenthashmap获取值直截了当。

  • 使用键的哈希码计算哈希值
  • 使用segment for获取段索引。
  • 使用段的获取功能来获取与密钥对应的值。
  • 如果它在ConcurrentHashMap中找不到值,它会锁定该段并再次尝试获取该值。

最佳实践:

如果我们需要低级别的并发级别,我们不应使用ConcurrentHashMap的默认构造函数,因为默认的ConcurrencyLevel为16,默认情况下它将创建16个段。

我们应该使用完全参数化构造函数:

ConcurrentHashMap(Int IntentCapacity,Float LoadFactor,INT ConcurrencyLevel)

在上面的构造函数中,IntentCapacity和LoadFactor与HashMap和ConcurrencyLevel相同,与我们上面定义相同。

因此,如果我们只需要两个可以同时编写的三个线程,则可能会将ConcurrentHashMap初始化为:

ConcurrentHashMap ch=new ConcurrentHashMap(16,0.75f,2);

如果我们有很多写线程和大量读取线程,ConcurrentHashMap会更好地执行更好。