(转载)两种方法让HashMap线程安全
HashMap不是线程安全的,往往在写程序时需要通过一些方法来回避.其实JDK原生的提供了2种方法让HashMap支持线程安全.
方法一:通过Collections.synchronizedMap()返回一个新的Map,这个新的map就是线程安全的. 这个要求大家习惯基于接口编程,因为返回的并不是HashMap,而是一个Map的实现.
方法二:重新改写了HashMap,具体的可以查看java.util.concurrent.ConcurrentHashMap. 这个方法比方法一有了很大的改进.
下面对这2中实现方法从各个角度进行分析和比较.
- 实现原理
- 锁机制的不同
- 如何得到/释放锁
- 优缺点
1)实现原理
方法一原理:
通过Collections.synchronizedMap()来封装所有不安全的HashMap的方法,就连toString, hashCode都进行了封装. 封装的关键点有2处,1)使用了经典的synchronized来进行互斥, 2)使用了代理模式new了一个新的类,这个类同样实现了Map接口.
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex;// Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
if (m==null)
throw new NullPointerException();
this.m = m;
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized(mutex) {return m.size();}
}
//***********************************
//节省空间,删除了大量类似代码
//***********************************
public String toString() {
synchronized(mutex) {return m.toString();}
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized(mutex) {s.defaultWriteObject();}
}
}
方法二原理:
重新写了HashMap,比较大的改变有如下几点.
使用了新的锁机制(可以理解为乐观锁)稍后详细介绍
把HashMap进行了拆分,拆分成了多个独立的块,这样在高并发的情况下减少了锁冲突的可能
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);
}
2)锁机制的不同
方法一使用的是的synchronized方法,是一种悲观锁.在进入之前需要获得锁,确保独享当前对象,然后做相应的修改/读取.
方法二使用的是乐观锁,只有在需要修改对象时,比较和之前的值是否被人修改了,如果被其他线程修改了,那么就会返回失败.锁的实现,使用的是NonfairSync. 这个特性要确保修改的原子性,互斥性,无法在JDK这个级别得到解决,JDK在此次需要调用JNI方法,而JNI则调用CAS指令来确保原子性与互斥性.读者可以自行Google JAVA CAS来了解更多. JAVA的乐观锁是如何实现的.
当如果多个线程恰好操作到ConcurrentHashMap同一个segment上面,那么只会有一个线程得到运行,其他的线程会被LockSupport.park(),稍后执行完成后,会自动挑选一个线程来执行LockSupport.unpark().
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> 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<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
3)如何得到/释放锁
得到锁:
方法一:在Hashmap上面,synchronized锁住的是对象(不是Class),所以第一个申请的得到锁,其他线程将进入阻塞,等待唤醒.
方法二:检查AbstractQueuedSynchronizer.state,如果为0,则得到锁,或者申请者已经得到锁,则也能再辞得到锁,并且state也加1.
释放锁:
都是得到锁的逆操作,并且使用正确,二种方法都是自动选取一个队列中的线程得到锁可以获得CPU资源.
4)优缺点
方法一:
优点:代码实现十分简单,一看就懂.
缺点:从锁的角度来看,方法一直接使用了锁住方法,基本上是锁住了尽可能大的代码块.性能会比较差.
方法二:
优点:需要互斥的代码段比较少,性能会比较好. ConcurrentHashMap把整个Map切分成了多个块,发生锁碰撞的几率大大降低,性能会比较好.
缺点:代码实现稍稍复杂些.
(转载)两种方法让HashMap线程安全的更多相关文章
- 【java基础 13】两种方法判断hashmap中是否形成环形链表
导读:额,我介绍的这两种方法,有点蠢啊,小打小闹的那种,后来我查了查资料,别人都起了好高大上的名字,不过,本篇博客,我还是用何下下的风格来写.两种方法,一种是丢手绢法,另外一种,是迷路法. 这两种方法 ...
- Java 创建线程的两种方法
Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线 ...
- (转载)C++创建对象的两种方法
(转载)http://blog.sina.com.cn/s/blog_586b6c050100dhjg.html 在C++里,有两种方法创建对象: 方法一: ClassName object(para ...
- [转载]C#读写txt文件的两种方法介绍
C#读写txt文件的两种方法介绍 by 大龙哥 1.添加命名空间 System.IO; System.Text; 2.文件的读取 (1).使用FileStream类进行文件的读取,并将它转换成char ...
- Linux 下操作GPIO(两种方法,驱动和mmap)(转载)
目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...
- 转载]PhpCms V9调用指定栏目子栏目文章的两种方法
PhpCms V9调用指定栏目子栏目文章的两种方法 第一种.直接写子栏目id ,用cat in {pc:get sql="SELECT * from v9_news where status ...
- 【转载】C++创建对象的两种方法
原文:http://blog.sina.com.cn/s/blog_586b6c050100dhjg.html 在C++里,有两种方法创建对象: 方法一: ClassName object(param ...
- SQLServer 批量插入数据的两种方法
SQLServer 批量插入数据的两种方法-发布:dxy 字体:[增加 减小] 类型:转载 在SQL Server 中插入一条数据使用Insert语句,但是如果想要批量插入一堆数据的话,循环使用Ins ...
- Java构造和解析Json数据的两种方法详解二
在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面接着介绍用org.json构造和解析Jso ...
随机推荐
- C++ 在文本文件后追加写入字符串
下面的代码起到这样一个作用: 如果有flow.txt文件,则打开该文件,在文件中继续写入字符串: 如果没有该文件,则创建该文件并写入. #include<string> #includ ...
- notepad++ 中配置 python一键运行
cmd /k python "$(FULL_CURRENT_PATH)"& PAUSE & EXIT cmd /k python 表示打开Command Promp ...
- history.back新页面跳转
今天遇到一个问题,页面跳转失效,原因在于原来在本页面打开的页面现在改为在新页面打开,历史url记录找不到了,所以不能向上一个地址跳转,解决的办法是使用$_SERVER['HTTP_REFERER']获 ...
- JDBC总结
今天复习了JDBC的内容,古人说温故知新,真的深有体会了.所以决定用自已的话在此记下,如有不对之处,请高手指教. JDBC连接数据库,采用的是面向接口编程.接口在java.sql和javax.sql包 ...
- R爬虫知识点
>>如何用 R 模仿浏览器的行为? GET / POST URLencode / URLdecode (破解中文網址的祕密) header & cookie 如何突破使用 cook ...
- assert_option()可以用来对assert()进行一些约束和控制
一.evaleval用法:eval() 函数把字符串按照 PHP 代码来计算.该字符串必须是合法的 PHP 代码,且必须以分号结尾.如果没有在代码字符串中调用 return 语句,则返回 NULL.如 ...
- MySQL时间戳相互转换
mysql将时间戳转成常用时间格式 在mysql中,一个时间字段的存储类型是int(11),怎么转化成字符类型,比方存储为13270655222,需要转化为yyyy -mm-dd的形式. 使用 FRO ...
- infinitynewtab 背景api
http://img.infinitynewtab.com/wallpaper/527.jpg 图片 1-4050
- google protobuf初体验
最近在读别人代码的时候发现一个的东西,名字叫protobuf, 感觉挺好用的,写在这里,留个记录.那么什么是protobuf 呢?假如您在网上搜索,应该会得到类似这样的文字介绍: Google Pro ...
- 快速理解几种常用的RAID磁盘阵列级别
我发现周围不少人在学习和理解RAID磁盘阵列的原理时,找了很多专业的资料来看,但是因为动手的机会比较少,因此看完以后还是似懂非懂,真正遇到实际的方案设计的时候,还是拿不定主意. 因此,我结合自己在过去 ...