转自:http://blog.csdn.net/exceptional_derek/article/details/40384659

先看一段代码:

  1. public class Locale {
  2. private final static Map<String, Locale> map = new HashMap<String,Locale>();
  3. public static Locale getInstance(String language, String country,
  4. String variant) {
  5. //...
  6. String key = some_string;
  7. Locale locale = map.get(key);
  8. if (locale == null) {
  9. locale = new Locale(language, country, variant);
  10. map.put(key, locale);
  11. }
  12. return locale;
  13. }
  14. // ....
  15. }

这段代码要做的事情是:

  1. 调用 map.get(key) 方法,判断 map 里面是否有该 key 对应的 value (Locale 对象)。
  2. 如果返回 null,表示 map 里面没有要查找的 key-value mapping。new 一个 Locale 对象,并把 new 出来的这个对象与 key 一起放入 map。
  3. 最后返回新创建的 Locale 对象
我们期望每次调用 getInstance 方法时要保证相同的 key 返回同一个 Local 对象引用。那么,单看第一段代码,请问它能实现这个期望么?
 
答案是:在单线程环境下可以满足要求,但是在多线程环境下会存在线程安全性问题,即不能保证在并发的情况相同的 key 返回同一个 Local 对象引用。
 
这是因为在上面的代码里存在一个习惯被称为 put-if-absent 的操作 [1],而这个操作存在一个 race condition:
  1. if (locale == null) {
  2. locale = new Locale(language, country, variant);
  3. map.put(key, locale);
  4. }
 因为在某个线程做完 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 写的那样:
  1. /**
  2. * If the specified key is not already associated
  3. * with a value, associate it with the given value.
  4. * This is equivalent to
  5. * <pre>
  6. *   if (!map.containsKey(key))
  7. *       return map.put(key, value);
  8. *   else
  9. *       return map.get(key);</pre>
  10. * except that the action is performed atomically.
  11. * .....
  12. */
所以可以使用该方法替代上面代码里的操作。但是,替代的时候很容易犯一个错误。请看下面的代码:
  1. public class Locale implements Cloneable, Serializable {
  2. private final static ConcurrentMap<String, Locale> map = new ConcurrentHashMap<String, Locale>();
  3. public static Locale getInstance(String language, String country,
  4. String variant) {
  5. //...
  6. String key = some_string;
  7. Locale locale = map.get(key);
  8. if (locale == null) {
  9. locale = new Locale(language, country, variant);
  10. map.putIfAbsent(key, locale);
  11. }
  12. return locale;
  13. }
  14. // ....
  15. }
 这段代码使用了 Map 的 concurrent 形式(ConcurrentMap、ConcurrentHashMap),并简单的使用了语句map.putIfAbsent(key, locale) 。这同样不能保证相同的 key 返回同一个 Locale 对象引用。
 
这里的错误出在忽视了 putIfAbsent 方法是有返回值的,并且返回值很重要。依旧看 javadoc:
  1. /**
  2. * @return  the previous value associated with the specified key, or
  3. *         <tt>null</tt> if there was no mapping for the key.
  4. *         (A <tt>null</tt> return can also indicate that the map
  5. *         previously associated <tt>null</tt> with the key,
  6. *         if the implementation supports null values.)
  7. */
“如果(调用该方法时)key-value 已经存在,则返回那个 value 值。如果调用时 map 里没有找到 key 的 mapping,返回一个 null 值”
 
所以,使用 putIfAbsent 方法时切记要对返回值进行判断。如下所示(java.util.Locale 类中的实现代码):
  1. public final class Locale implements Cloneable, Serializable {
  2. // cache to store singleton Locales
  3. private final static ConcurrentHashMap<String, Locale> cache = new ConcurrentHashMap<String, Locale>(32);
  4. static Locale getInstance(String language, String country, String variant) {
  5. if (language== null || country == null || variant == null) {
  6. throw new NullPointerException();
  7. }
  8. StringBuilder sb = new StringBuilder();
  9. sb.append(language).append('_').append(country).append('_').append(variant);
  10. String key = sb.toString();
  11. Locale locale = cache.get(key);
  12. if (locale == null) {
  13. locale = new Locale(language, country, variant);
  14. Locale l = cache.putIfAbsent(key, locale);
  15. if (l != null) {
  16. locale = l;
  17. }
  18. }
  19. return locale;
  20. }
  21. // ....
  22. }
与前段代码相比,增加了对方法返回值的判断:
  1. Locale l = cache.putIfAbsent(key, locale);
  2. if (l != null) {
  3. locale = l;
  4. }
 
这样可以保证并发情况下代码行为的准确性。
 
-------------------------------------------------

深入理解ConcurrentMap.putIfAbsent(key,value) 用法的更多相关文章

  1. ConcurrentMap.putIfAbsent(key,value) 用法讨论

    ConcurrentMap.putIfAbsent(key,value) 用法讨论 http://wxl24life.iteye.com/blog/1746794

  2. binarySearch(int[] a,int fromIndex,int toIndex, int key)的用法

    package com.Summer_0420.cn; import java.util.Arrays; /** * @author Summer * binarySearch(int[] a,int ...

  3. 理解并掌握Promise的用法

    前沿:  Promise在处理异步操作非常有用.项目中,与后端进行数据请求的时候经常要用到Promise.我们可以用promise + xhr进行ajax的封装.也可以使用基于promise封装的请求 ...

  4. R︱高效数据操作——data.table包(实战心得、dplyr对比、key灵活用法、数据合并)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 由于业务中接触的数据量很大,于是不得不转战开始 ...

  5. 批量插入或更新操作之ON DUPLICATE KEY UPDATE用法

    实际的开发过程中,可能会遇到这样的需求,先判断某一记录是否存在,如果不存在,添加记录,如果存在,则修改数据.在INSERT语句末尾指定ON DUPLICATE KEY UPDATE可以解决这类问题. ...

  6. $key 的用法

    <?php $attr=array("a","b","c","d"); //$key,默认是主键值,$value, ...

  7. ON DUPLICATE KEY UPDATE用法

    INSERT INTO `books ` (`name`,`count`,`num`) VALUES ('windows','1','2'),('','linux','1','3') ON DUPLI ...

  8. MySQL: ON DUPLICATE KEY UPDATE 用法 避免重复插入数据

    INSERT INTO osc_visit_stats(stat_date,type,id,view_count) VALUES (?,?,?,?) ON DUPLICATEKEY UPDATE vi ...

  9. 通过回调函数的理解来进一步理解ajax及其注意的用法

    一,再一次理解回调函数 (function($){ $.fn.shadow = function(opts){ //定义的默认的参数 var defaults = { copies: 5, opaci ...

随机推荐

  1. 关于LESS

    LESS 是动态的样式表语言,通过简洁明了的语法定义,使编写 CSS 的工作变得非常简单. 翻译成大白话:写CSS算是体力活,并没有编程的感觉,不给前端人员装逼的机会,于是就搞了这玩意,相当于编程写C ...

  2. Vue.js 整理笔记

    以前我们用Jquery进行dom的操作,虽然熟悉后开发效率很高,但是如果多个控件的相互操作多的情况下,还是会乱.相比之下,Vue的使用更加清晰,通过虚拟dom将数据绑定,而且组件化和路由的帮助下,让整 ...

  3. RN8209校正软件开发心得(1)

    最近领导突然让我做软件了,头大啊.以前也没怎么自己独立做过软件,这次来的突然啊,面对这么大的任务量自己只能加把劲了,还等着领导给涨工资呢,哈哈... 作为编程的小白,要自己做一款上位机的软件实属不易啊 ...

  4. 我的vimrc

    set nocompatible set langmenu=en_USlet $LANG= 'en_US' source $VIMRUNTIME/vimrc_example.vim source $V ...

  5. Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

    Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...

  6. BZOJ 1236: SPOJ1433 KPSUM

    Description 用+-号连接1-n所有数字的数位,问结果是多少. Sol 数位DP. \(f[i][j][0/1][0/1]\) 表示长度为 \(i\) 的数字,开头数字是 \(j\) ,是否 ...

  7. setprecision **fixed

    #include <iostream> #include <iomanip> using namespace std; int main( void ) { const dou ...

  8. android键盘输入读取

    android键盘输入读取  监控android键盘输入方式有两种,一种在java层实现,重写onKeyDown和onKeyUp方法.另一种是在jni层实现,监控/dev/input/event0键盘 ...

  9. 读一篇Javascript问题贴的收获

    遇到这篇文章<Javascript异步调用时,回调函数内用到了函数外的变量>,是缘于我在<难道这就是JavaScript中的"闭包">文章中遇到的问题时,B ...

  10. markdown简介

    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接 ...