Java中的ConcurrentHashMap
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;
}
}
- 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会更好地执行更好。

