找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
守望者成才网 探索 技术沉淀 查看内容

【守望者 j2se】ConcurrentMap之putIfAbsent(key,value)用法讨论

2014-10-17 11:39| 发布者: zhouy| 查看: 2239| 评论: 0

摘要: 先看一段代码:public class Locale { private final static MapString, Locale map = new HashMapString,Locale(); public static Locale getInstance(String language, String country, String variant) ...

先看一段代码:

public class Locale {  
    private final static Map map = new HashMap();  
    public static Locale getInstance(String language, String country,  
            String variant) {  
        //...  
        String key = some_string;  
        Locale locale = map.get(key);  
        if (locale == null) {  
            locale = new Locale(language, country, variant);  
            map.put(key, locale);  
        }  
        return locale;  
    }  
    // ....  
}  
 
这段代码要做的事情是:
 
调用 map.get(key) 方法,判断 map 里面是否有该 key 对应的 value (Locale 对象)。
如果返回 null,表示 map 里面没有要查找的 key-value mapping。new 一个 Locale 对象,并把 new 出来的这个对象与 key 一起放入 map。
最后返回新创建的 Locale 对象。我们期望每次调用 getInstance 方法时要保证相同的 key 返回同一个 Local 对象引用。那么,单看第一段代码,请问它能实现这个期望么?
 
答案是:在单线程环境下可以满足要求,但是在多线程环境下会存在线程安全性问题,即不能保证在并发的情况相同的 key 返回同一个 Local 对象引用。
 
这是因为在上面的代码里存在一个习惯被称为 put-if-absent 的操作 [1],而这个操作存在一个 race condition:
Java代码  收藏代码
if (locale == null) {  
    locale = new Locale(language, country, variant);  
    map.put(key, locale);  
}  

因为在某个线程做完 locale == null 的判断到真正向 map 里面 put 值这段时间,其他线程可能已经往 map 做了 put 操作,这样再做 put 操作时,同一个 key 对应的 locale 对象被覆盖掉,最终 getInstance 方法返回的同一个 key 的 locale 引用就会出现不一致的情形。所以对 Map 的 put-if-absent 操作是不安全的(thread safty)。
 

为了解决这个问题,java 5.0 引入了 ConcurrentMap 接口,在这个接口里面 put-if-absent 操作以原子性方法 putIfAbsent(K key, V value) 的形式存在。正如 javadoc 写的那样:

/** 
     * If the specified key is not already associated 
     * with a value, associate it with the given value. 
     * This is equivalent to 
     *
 
     *   if (!map.containsKey(key)) 
     *       return map.put(key, value); 
     *   else 
     *       return map.get(key); 
     * except that the action is performed atomically. 
     * ..... 
     */  


所以可以使用该方法替代上面代码里的操作。但是,替代的时候很容易犯一个错误。请看下面的代码:

public class Locale implements Cloneable, Serializable {  
    private final static ConcurrentMap map = new ConcurrentHashMap();  
    public static Locale getInstance(String language, String country,  
            String variant) {  
        //...  
        String key = some_string;  
        Locale locale = map.get(key);  
        if (locale == null) {  
            locale = new Locale(language, country, variant);  
            map.putIfAbsent(key, locale);  
        }  
        return locale;  
    }  
    // ....  
}  
 
这段代码使用了 Map 的 concurrent 形式(ConcurrentMap、ConcurrentHashMap),并简单的使用了语句map.putIfAbsent(key, locale) 。这同样不能保证相同的 key 返回同一个 Locale 对象引用。
 这里的错误出在忽视了 putIfAbsent 方法是有返回值的,并且返回值很重要。依旧看 javadoc:


/** 
  * @return  the previous value associated with the specified key, or 
  *         null if there was no mapping for the key. 
  *         (A null return can also indicate that the map 
  *         previously associated null with the key, 
  *         if the implementation supports null values.) 
  */ 
 
“如果(调用该方法时)key-value 已经存在,则返回那个 value 值。如果调用时 map 里没有找到 key 的 mapping,返回一个 null 值”。所以,使用 putIfAbsent 方法时切记要对返回值进行判断。如下所示(java.util.Locale 类中的实现代码):

public final class Locale implements Cloneable, Serializable {  
    // cache to store singleton Locales  
    private final static ConcurrentHashMap cache = new ConcurrentHashMap(32);  
    static Locale getInstance(String language, String country, String variant) {  
        if (language== null || country == null || variant == null) {  
            throw new NullPointerException();  
        }  
  
    StringBuilder sb = new StringBuilder();  
    sb.append(language).append('_').append(country).append('_').append(variant);  
    String key = sb.toString();  
    Locale locale = cache.get(key);  
    if (locale == null) {  
        locale = new Locale(language, country, variant);  
        Locale l = cache.putIfAbsent(key, locale);  
        if (l != null) {  
        locale = l;  
        }  
    }  
    return locale;  
    }  
    // ....  
}  

与前段代码相比,增加了对方法返回值的判断:
Java代码  收藏代码
Locale l = cache.putIfAbsent(key, locale);    
if (l != null) {    
    locale = l;    
}    
 
这样可以保证并发情况下代码行为的准确性。


本文由守望者watchmen收集整理,部分内容源于网络(http://wxl24life.iteye.com/blog/1746794)。本文仅代表作者个人观点,不代表守望者的本意。如有违法侵权内容,请提交到守望者管理员处,立即处理。

路过

会员评论  

已有0参与评论

行业聚焦  面试交流  职位推荐  开发视频   技术交流  腾讯微博  新浪微博

友情链接:课课家教育  阿里云  鲜果  W3Cfuns前端网  中国企业家  环球企业家  投资界  传媒梦工场  MSN中文网  Android开发者社区  cnbeta  投资中国网  又拍云存储  美通说传播  IT茶馆  网商在线  商业评论网  TechOrange  IT时代周刊  3W创新传媒  开源中国社区  二维工坊  Iconfans  推酷  智能电视网  FreeBuf黑客与极客  财经网  DoNews  凤凰财经  新财富  eoe移动开发者社区  i黑马  网易科技  新浪科技  搜狐IT  创业家  创业邦  腾讯财经  福布斯中文网  天下网商  TechWeb  雷锋网  新浪创业  和讯科技  品途O2O  极客公园  艾瑞网  抽屉新热榜  卖家网  人民网通信频道  拉勾网  创新派  简单云主机  

手机版|黑名单|守望者在线 在线教育 linux 高级程序设计 C/C++ 大数据 ( 蜀ICP备14029946号

成都守望者科技有限公司 © 2013-2016 All Rights Reserved