转自: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. python 类定义 继承

    0 前言 系统:win7 64bit IDE : python(x,y) 2.7.6.1 IDE集成的解释器:Python 2.7.6 (default, Nov 10 2013, 19:24:18) ...

  2. SPSS课程学习思路及流程

    数据挖掘领域对行的分析

  3. 我们为什么要看《超实用的Node.JS代码段》

    不知道自己Node.JS水平如何?看这张图 如果一半以上的你都不会,必须看这本书,一线工程师用代码和功能页面来告诉你每一个技巧点. 都会一点,但不知道如何检验自己,看看本书提供的面试题: 1.     ...

  4. Dijkstra 算法

    all the nodes should be carectorized into three groups: (visited, front, unknown) we should pay spec ...

  5. linux vi(vim)常用命令汇总

    1 查找 /xxx(?xxx) 表示在整篇文档中搜索匹配xxx的字符串, / 表示向下查找, ? 表示向上查找其中xxx可以是正规表达式,关于正规式就不多说了. 一般来说是区分大小写的, 要想不区分大 ...

  6. Latex使用整理

    \section{software academy}(标题) \subsection{software enginner} (小标题) \subsection{computer science} \s ...

  7. iOS如何彻底避免数组越界

    我们先来看看有可能会出现的数组越界Crash的地方: ? 1 2 3 4 5 6 7 - (void)tableView:(UITableView *)tableView didSelectRowAt ...

  8. NOIP2016滚粗计

    啦啦啦,第一次写游记~ Day0 早上浪浪浪,开了几盘CS 坐车到衢州,在车上开了几盘 艾萨克,然而好困啊…… 到衢二后围观XJ杭二合力A ztr,不是很懂为什么事情会变成这样 晚上开杀人游戏,wcz ...

  9. mysql的enum和set数据类型

    定义一个ENUM或者SET类型,可以约束存入的数值. ENUM中的值必须是定义过数值列中的一个,比如ENUM('a','b','c'),存入的只能是'a'或者'b'或者'c',如果存入'','d'或者 ...

  10. PHP基础

    $a=10; //$b="hello";//$a=(string)$a; 强制转换A的类型为字符串 //settype($a,"string");//var_d ...