hashtable是线程安全的api有哪些吗

多线程之Map:Hashtable&HashMap&以及ConcurrentHashMap
1、Map体系
Hashtable是JDK
5之前Map唯一线程安全的内置实现(Collections.synchronizedMap不算)。Hashtable继承的是Dictionary(Hashtable是其唯一公开的子类),并不继承AbstractMap或者HashMap.尽管Hashtable和HashMap的结构非常类似,但是他们之间并没有多大联系。
ConcurrentHashMap是HashMap的线程安全版本,ConcurrentSkipListMap是TreeMap的线程安全版本。
最终可用的线程安全版本Map实现是ConcurrentHashMap/ConcurrentSkipListMap/Hashtable/Properties四个,但是Hashtable是过时的类库,因此如果可以的应该尽可能的使用ConcurrentHashMap和ConcurrentSkipListMap.
2、Hashtable、HashMap异同
(1)Hashtable是Dictionary的子类
public class Hashtable
extends Dictionary
implements Map, Cloneable, java.io.Serializable
public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable
HashMap和Hashtable都是Map接口的一个实现类;
(2)Hashtable中的方法是同步的(),而HashMap中的方法在默认情况下不是同步的。即是说,在多线程应用程序中,不用专门的操作就安全地可以使用Hashtable了;而对于HashMap,则需要额外的同步机制。但HashMap的同步问题可通过Collections的一个静态方法得到解决:
public static Map synchronizedMap(Map m)
这个方法返回一个同步的Map,也就是说返回的Map是线程安全的。需要注意的是,对返回的map进行迭代时,必须手动在返回的map上进行同步,否则将会导致不确定的行为:
Map m = Collections.synchronizedMap(new HashMap());
Set s = m.keySet(); // Needn't be in synchronized block
synchronized(m) { // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
(3)在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。Hashtable的键值不能为null,否则:java.lang.NullPointerException
(4)HashTable使用Enumeration,HashMap使用Iterator。
以上只是表面的不同,它们的实现也有很大的不同。
(5)HashTable中hash数组默认大小是11,增加的方式是
old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
(6)哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.
而HashMap重新计算hash值,而且用与代替求模,比如HashMap的put方法
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != e = e.next) {
if (e.hash == hash && ((k = e.key) == key ||
key.equals(k))) {
V oldValue = e.
e.recordAccess(this);
return oldV
modCount++;
addEntry(hash, key, value, i);
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load
h ^= (h &&& 20) ^ (h &&& 12);
return h ^ (h &&& 7) ^ (h &&& 4);
static int indexFor(int h, int length) {
return h & (length-1);
总之,HashTable是多线程安全的,不需要人工同步,但性能方面会差一点;而HashMap不能直接用于多线程。而
ConcurrentHashMap的出现正解决上诉问题。它是HashMap的线程安全版本,性能方面也优于HashTable。ConcurrentSkipListMap是TreeMap的线程安全版本。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。博客分类:
相同点: Hashtable 和 ConcurrentHashMap都是线程安全的,可以在多线程环境中运行; key跟value都不能是null
区别: 两者主要是性能上的差异,Hashtable的所有操作都会锁住整个对象,虽然能够保证线程安全,但是性能较差; ConcurrentHashMap内部使用Segment数组,每个Segment类似于Hashtable,在“写”线程或者部分特殊的“读”线程中锁住的是某个Segment对象,其它的线程能够并发执行其它的Segment对象。
下面从下面两个问题来具体了解下Hashtable和ConcurrentHashMap
1, Hashtable怎样实现线程安全?
2,ConcurrentHashMap怎样实现线程安全?为什么性能会比Hashtable好?
先来看第一个问题: Hashtable怎样实现线程安全?
下面是Hashtable的get和put方法
* The hash table data.
//Hashtable使用Entry数组来保存数据
private transient Entry[]
public synchronized V get(Object key) {
Entry tab[] =
int hash = key.hashCode();
//通过求模运算得到下标的索引值,保证下标在 【0,tab.length)
int index = (hash & 0x7FFFFFFF) % tab.
for (Entry&K,V& e = tab[index] ; e != e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
// Makes sure the key is not already in the hashtable.
Entry tab[] =
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.
for (Entry&K,V& e = tab[index] ; e != e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.
modCount++;
//元素数量超过阀值,需要增大散列表的大小
if (count &= threshold) {
// Rehash the table if the threshold is exceeded
index = (hash & 0x7FFFFFFF) % tab.
// Creates the new entry.
Entry&K,V& e = tab[index];
tab[index] = new Entry&K,V&(hash, key, value, e);
可以看到,Hashtable的方法都是声明为synchronized的,这样就能够保证Hashtable是线程安全的。 Hashtable的内部结构如下图所示:
Hashtable在put元素时,会根据定义的方法计算hash值,如果这个位置没有元素,直接添加,如d1,如果已经有元素,会按照链表的方式将元素插在链表的头部,如aa。
再来看第二个问题: ConcurrentHashMap怎样实现线程安全?为什么性能会比Hashtable好?
1,下面是ConcurrentHashMap的put方法
// ConcurrentHashMap的key 跟 value都不能是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);
好的hash算法很重要(对于Hashtable,HashMap等也同样),如果冲突的概率大,会严重影响性能,下面是ConcurrentHashMap的hash算法,不过为什么会冲突较小就不明白了,望高手解决
private static int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h &&
15) ^ 0xffffcd7d;
h ^= (h &&& 10);
h += (h &&
h ^= (h &&&
h += (h &&
2) + (h && 14);
return h ^ (h &&& 16);
* 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&K,V& segmentFor(int hash) {
return segments[(hash &&& segmentShift) & segmentMask];
在segmentFor的方法中,变量segmentShift和segmentMask在创建ConcurrentHashMap的时候初始化, 如下所示
//initialCapacity: 初始容量大小,默认为16
//loadFactor: 跟threshold的值相关,控制是否需要扩容的阀门值,默认为0.75f
//concurrencyLevel: the estimated number of concurrently updating threads, 跟Segment数组大小相关,默认为16
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor & 0) || initialCapacity & 0 || concurrencyLevel &= 0)
throw new IllegalArgumentException();
if (concurrencyLevel & MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize & concurrencyLevel) {
ssize &&= 1;
//获取不小于concurrencyLevel的最小的2的幂,跟segmentFor计算相关
segmentShift = 32 -
segmentMask = ssize - 1; //二进制的低位都为1,在segmentFor方法中,保证(hash &&& segmentShift) & segmentMask结果在0~ssize-1之间(很好的算法,比求模的运算效率高)
this.segments = Segment.newArray(ssize);
if (initialCapacity & MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity /
if (c * ssize & initialCapacity)
int cap = 1;
while (cap & c)
cap &&= 1;
for (int i = 0; i & this.segments. ++i)
this.segments[i] = new Segment&K,V&(cap, loadFactor);
如果使用默认的参数构造ConcurrentHashMap,即initialCapacity=16,loadFactor=0.75f,concurrencyLevel=16, 得到的ssize=16,sshift=4, segmentMask=15, Segment数组长度为16。在表达式(hash &&& segmentShift) & segmentMask中,segmentMask的二进制为 001111,通过位与运算得到的结果范围为0~15,相比Hashtable的模运算,效率更高。 但是可能会有疑问,为何需要保证ssize为2的幂?如果ssize不是2的幂,得到的segmentMask低位不是全部为1,比如ssize=14,segmentMask=13,二进制为001101,此时位与运算肯定无法得到索引为 00**1*的数值,比如2、3、6、7、10、11、14,会导致Segment数组的利用率低,产生较大的hash冲突。
再来看下Segment的put方法
V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 判断是否需要扩容
if (c++ & threshold) // ensure capacity
HashEntry&K,V&[] tab =
int index = hash & (tab.length - 1);
HashEntry&K,V& first = tab[index];
HashEntry&K,V& e =
while (e != null && (e.hash != hash || !key.equals(e.key)))
if (e != null) {
oldValue = e.
if (!onlyIfAbsent)
oldValue =
tab[index] = new HashEntry&K,V&(key, hash, first, value);
count = // write-volatile
return oldV
} finally {
在put方法中,使用ReentrantLock的 lock()和unlock()方法控制同步,Segment的定义如下
* Segments are specialized versions of hash tables.
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
static final class Segment&K,V& extends ReentrantLock implements Serializable
从上面代码可知,ConcurrentHashMap在并发修改时,锁住的是当前Segment的对象,其它Segment中的并发操作可以同时执行,性能会比使用Hashtable好。 ConcurrentHashMap的结构如下所示:
分析的一段话,写得很好,哈哈,就搬过来了(勿怪)。
Hashtable,ConcurrentHashMap 有什么区别,这两个都是hash表,都是同步的
Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处 是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分 数据。此外,使用默认构造器创建的ConcurrentHashMap比较占内存,如果程序需要创建巨量ConcurrentHashMap,应该在构造 时指定concurrencyLevel (详情参考
pi88dian88
浏览: 19303 次
来自: 杭州
你应该记录到 我的应用--&我的收藏
string2020 写道楼主能不能谈谈 Hashtable, ...
string2020 写道楼主能不能谈谈 Hashtable, ...
楼主能不能谈谈 Hashtable,ConcurrentHas ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'HashTable、ConcurrentHashMap、SynchronizedMap(都是线程安全的)_dsydly_新浪博客
HashTable、ConcurrentHashMap、SynchronizedMap(都是线程安全的)
1)Hashtable中采用的锁机制(Synchronized)是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;
​&Hashtable的任何操作都会把整个表锁住,是阻塞的。优势:是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。缺点:是所有调用都要排队,效率较低。当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间
2)ConcurrentHashMap锁的方式是稍微细粒度的。
ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。原来只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。
读写锁分离:惊讶的是ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。
​ConcurrentHashMap在1.8版本里面好像都是调用native方法实现的。。。也就是说线程安全都是依靠底层硬件实现的。
​& &
&优点:ConcurrentHashMap
是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。缺点:是严格来说读取操作不能保证反映最近的更新。
​3)通过将基本的功能从线程安全性中分离开来,Collections.synchronizedMap允许需要同步的用户可以拥有同步,而不需要同步的用户则不必为同步付出代价。&
​& Map m =
Collections.synchronizedMap(new HashMap());
将线程不安全的HashMap变成线程安全的。
& List l = Collections.synchronizedList(new
ArrayList());
​&例如: Map m =
Collections.synchronizedMap(new HashMap());
& & & Set s =
m.keySet(); &// Needn't be in synchronized
synchronized(m) { &// Synchronizing on m,
& &Iterator i = s.iterator(); //
Must be in synchronized block
& &while (i.hasNext())
& &foo(i.next());
博客等级:
博客积分:0
博客访问:4,326
关注人气:0
荣誉徽章:深入解析Hashtable的线程同步问题 - Java之我见 - CSDN博客
深入解析Hashtable的线程同步问题
在我们最熟悉的概念里,我们都知道相对于Hashmap而言,Hashtable的处理效率更慢,因为Hashtable引入了多线程安全机制,所以在处理键值对的时候,效率很慢。
但事实上Hashtable本身是没有实现serializable接口,也就是说它本身在多线程的机制中并不是线程安全的。
然而,在真正的应用开发上,当我们需要用到多线程的Hashtable时,我们都会在创建的时候引入同步概念,使得Hashtable变成线程安全的,详细代码解析如下:
class HashTableIsNot
private Hashtable a = new Hashtable();//不是线程安全的 &span style=&white-space:pre&& &/span&/*因为Hashtable本身并不是线程同步,所以在直接创建Hasshtable对象时,它不再是线程安全的*/
private Hashtable b = Hashtable.Synchronized(new Hashtable());//线程安全的  &span style=&white-space:pre&& &/span&/*只有在创建对象时引用了线程同步,才会使得创建出来的Hashtable对象变成线程安全*/
public void IsNotSafe()
Console.WriteLine(&是否线程安全&+a.IsSynchronized);
Console.WriteLine(&是否线程安全& + b.IsSynchronized);
// table1 = Hashtable.Synchronized(a);
Console.WriteLine(&是否线程安全& + a.IsSynchronized);//
private void WriteA()
lock (a.SyncRoot)
Console.WriteLine(&go
for (int i = 0; i & 10; i++)
a.Add(i, i);
Console.WriteLine(i);
Thread.Sleep(500);
Console.WriteLine(&exit from
private void WriteB()
lock (table1.SyncRoot)
Console.WriteLine(&go
for (int i = 10; i & 20; i++)
table1.Add(i, i);
Console.WriteLine(i);
Thread.Sleep(500);
Console.WriteLine(&exit from
private void ReadA()
lock (a.SyncRoot)
Console.WriteLine(&go
foreach (object a in A)
Console.WriteLine(a.ToString());
Thread.Sleep(500);
Console.WriteLine(&exit from
public void SyncTest2()
Thread tWrite1 = new Thread(WriteA);
Thread tWrite2 = new Thread(WriteB);
Thread tRead1= new Thread(ReadA);
Thread tRead2 = new Thread(ReadB);
tWrite1.Start();
tRead2.Start();
tWrite2.Start();
tRead1.Start();
经过以上的例子,我们可以很明显的测试出,只有实现了serializable接口的Hashtable才能算作是线程安全。
我的热门文章

我要回帖

更多关于 object类有哪些方法 的文章

 

随机推荐