深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) -凯发k8网页登录

关注后端架构、中间件、分布式和并发编程

   :: 凯发k8网页登录首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 10 文章 :: 2680 评论 :: 0 trackbacks

从这一节开始正式进入并发容器的部分,来看看jdk 6带来了哪些并发容器。

在jdk 1.4以下只有vector和hashtable是线程安全的集合(也称并发容器,collections.synchronized*系列也可以看作是线程安全的实现)。从jdk 5开始增加了线程安全的map接口concurrentmap和线程安全的队列blockingqueue(尽管queue也是同时期引入的新的集合,但是规范并没有规定一定是线程安全的,事实上一些实现也不是线程安全的,比如priorityqueue、arraydeque、linkedlist等,在queue章节中会具体讨论这些队列的结构图和实现)。

 

在介绍concurrencymap之前先来回顾下map的体系结构。下图描述了map的体系结构,其中蓝色字体的是jdk 5以后新增的并发容器。

针对上图有以下几点说明:

  1. hashtable是jdk 5之前map唯一线程安全的内置实现(collections.synchronizedmap不算)。特别说明的是hashtable的t是小写的(不知道为啥),hashtable继承的是dictionary(hashtable是其唯一公开的子类),并不继承abstractmap或者hashmap。尽管hashtable和hashmap的结构非常类似,但是他们之间并没有多大联系。
  2. concurrenthashmap是hashmap的线程安全版本,concurrentskiplistmap是treemap的线程安全版本。
  3. 最终可用的线程安全版本map实现是concurrenthashmap/concurrentskiplistmap/hashtable/properties四个,但是hashtable是过时的类库,因此如果可以的应该尽可能的使用concurrenthashmap和concurrentskiplistmap。

 

回到正题来,这个小节主要介绍concurrenthashmap的api以及应用,下一节才开始将原理和分析。

除了实现map接口里面对象的方法外,concurrenthashmap还实现了concurrentmap里面的四个方法。

 

v putifabsent(k key,v value)

如果不存在key对应的值,则将value以key加入map,否则返回key对应的旧值。这个等价于清单1 的操作:

清单1 putifabsent的等价操作

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

在前面的章节中提到过,连续两个或多个原子操作的序列并不一定是原子操作。比如上面的操作即使在hashtable中也不是原子操作。而putifabsent就是一个线程安全版本的操作的。

有些人喜欢用这种功能来实现,例如清单2。

清单2 一种单例模式的实现

package xylz.study.concurrency;

import java.util.concurrent.concurrenthashmap;
import java.util.concurrent.concurrentmap;

public class concurrentdemo1 {

    private static final concurrentmap map = new concurrenthashmap();
    private static concurrentdemo1 instance;
    public static concurrentdemo1 getinstance() {
        if (instance == null) {

            map.putifabsent("instance", new concurrentdemo1());

            instance = map.get("instance");
        }
        return instance;
    }

    private concurrentdemo1() {
    }

}

当然这里只是一个操作的例子,实际上在文章中有很多的实现和比较。清单2 在存在大量单例的情况下可能有用,实际情况下很少用于单例模式。但是这个方法避免了向map中的同一个key提交多个结果的可能,有时候在去掉重复记录上很有用(如果记录的格式比较固定的话)。

 

boolean remove(object key,object value)

只有目前将键的条目映射到给定值时,才移除该键的条目。这等价于清单3 的操作。

清单3 remove(object,object)的等价操作

if (map.containskey(key) && map.get(key).equals(value)) {
   map.remove(key);
   return true;
}
return false;

由于集合类通常比较的hashcode和equals方法,而这两个方法是在object对象里面,因此两个对象如果hashcode一致,并且覆盖了equals方法后也一致,那么这两个对象在集合类里面就是“相同”的,不管是否是同一个对象或者同一类型的对象。也就是说只要key1.hashcode()==key2.hashcode() && key1.equals(key2),那么key1和key2在集合类里面就认为是一致,哪怕他们的class类型不一致也没关系,所以在很多集合类里面允许通过object来类型来比较(或者定位)。比如说map尽管添加的时候只能通过制定的类型,但是删除的时候却允许通过一个object来操作,而不必是k类型。

既然map里面有一个remove(object)方法,为什么concurrentmap还需要remove(object,object)方法呢?这是因为尽管map里面的key没有变化,但是value可能已经被其他线程修改了,如果修改后的值是我们期望的,那么我们就不能拿一个key来删除此值,尽管我们的期望值是删除此key对于的旧值。

这种特性在原子操作章节的atomicmarkablereferenceatomicstampedreference里面介绍过。

 

boolean replace(k key,v oldvalue,v newvalue)

只有目前将键的条目映射到给定值时,才替换该键的条目。这等价于清单4 的操作。

清单4 replace(k,v,v)的等价操作

if (map.containskey(key) && map.get(key).equals(oldvalue)) {
   map.put(key, newvalue);
   return true;
}
return false;

 

v replace(k key,v value)

只有当前键存在的时候更新此键对于的值。这等价于清单5 的操作。

清单5 replace(k,v)的等价操作

if (map.containskey(key)) {
   return map.put(key, value);
}
return null;

replace(k,v,v)相比replace(k,v)而言,就是增加了匹配oldvalue的操作。

 

其实这4个扩展方法,是concurrentmap附送的四个操作,其实我们更关心的是map本身的操作。当然如果没有这4个方法,要完成类似的功能我们可能需要额外的锁,所以有总比没有要好。比如清单6,如果没有putifabsent内置的方法,我们如果要完成此操作就需要完全锁住整个map,这样就大大降低了concurrentmap的并发性。这在下一节中有详细的分析和讨论。

清单6 putifabsent的外部实现

public v putifabsent(k key, v value) {
    synchronized (map) {
        if (!map.containskey(key)) return map.put(key, value);
        return map.get(key);
    }
}

 

参考资料:

 



©2009-2014 imxylz
|求贤若渴
posted on 2010-07-19 15:25 imxylz 阅读(24427) 评论(8)     所属分类: java concurrency
# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1)[未登录] 2010-07-19 18:05
uml图很漂亮,有点rational rose风格,但是好像又有点区别。请问您用的uml软件是?  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2010-07-19 18:12 xylz
@denis
enterprise architect 7.5  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1)[未登录] 2010-07-19 18:14
@xylz
唉,果然,可惜要破解。  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2011-01-03 23:57
map.put第一次返回的是null
所以map.putifabsent第一次也是返回的null,那个单例模式第一次就返回的是null  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2011-01-04 13:02 xylz
@fordybeach
已经改正过来了,多谢多谢,你太仔细了  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2012-08-02 11:50
2啊,因为hashtable是一个单词啊,并非hash和table的联合。。。  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2013-03-26 14:22
hashtable是集成ojbect,lz看清楚了再写的,不要误导人的。  回复  
  

# re: 深入浅出 java concurrency (16): 并发容器 part 1 concurrentmap (1) 2014-12-16 10:27
@kkkkkkkkk
是你没看清楚吧  回复  
  


©2009-2014
网站地图