深入理解ConcurrentMap.putIfAbsent(key,value) 用法 2022-02-13 23:21 109阅读 0赞 # 深入理解ConcurrentMap.putIfAbsent(key,value) 用法 # 2014年10月22日 22:26:46 [吴孟达][Link 1] 阅读数:9904 先看一段代码: Java代码 ![收藏代码][icon_star.png] 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: Java代码 ![收藏代码][icon_star.png] 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 写的那样: Java代码 ![收藏代码][icon_star.png] 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. \*/ 所以可以使用该方法替代上面代码里的操作。但是,替代的时候很容易犯一个错误。请看下面的代码: Java代码 ![收藏代码][icon_star.png] 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: Java代码 ![收藏代码][icon_star.png] 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 类中的实现代码): Java代码 ![收藏代码][icon_star.png] 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. 9. StringBuilder sb = new StringBuilder(); 10. sb.append(language).append('\_').append(country).append('\_').append(variant); 11. String key = sb.toString(); 12. Locale locale = cache.get(key); 13. if (locale == null) \{ 14. locale = new Locale(language, country, variant); 15. Locale l = cache.putIfAbsent(key, locale); 16. if (l != null) \{ 17. locale = l; 18. \} 19. \} 20. return locale; 21. \} 22. // .... 23. \} 与前段代码相比,增加了对方法返回值的判断: Java代码 ![收藏代码][icon_star.png] 1. Locale l = cache.putIfAbsent(key, locale); 2. if (l != null) \{ 3. locale = l; 4. \} 这样可以保证并发情况下代码行为的准确性。 \------------------------------------------------- 本文写的内容源于今天阅读 java.util.DateFormat 源码时碰到的一个用法,更准确的说,这个用法出现在 java SE 6 的 java.util.Locale 类的 getInstance(String language, String country, String variant) 方法实现部分。 加之前阵子刚看过 FindBugs 的某个 ppt \[2\],ppt 上举了几个 Java 程序员容易犯错的代码写法的例子,其中一个就是忽视ConcurrentMap.putIfAbsent 方法返回值的情况。 最后,借用 ppt 上给的关于这个错误的 lessons(经验教训): * Concurrency is tricky * putIfAbsent is tricky to use correctly * *engineers at Google got it wrong more than 10% of the time* * Unless you need to ensure a single value, just use get followed by put if not found * **If you need to ensure a single unique value shared by all threads, use putIfAbsent and Check return value** \------------------------------------------- 参考: \[1\] Java Concurrency in Practice. by Brian Goetz, Tim Peierls, Joshua Bloch el. \[2\] Defective Java Code: Mistakes That Matter. by William Pugh [Link 1]: https://me.csdn.net/Derek_BMW [icon_star.png]: /images/20220213/ae69558e406d45108d09368711a12663.png 文章版权声明:注明蒲公英云原创文章,转载或复制请以超链接形式并注明出处。
相关 深入理解ConcurrentMap.putIfAbsent(key,value) 用法 深入理解ConcurrentMap.putIfAbsent(key,value) 用法 2014年10月22日 22:26:46 [吴孟达][Link 1] 阅读数:99 深碍√TFBOYSˉ_/ 2022年02月13日 23:21/ 0 赞/ 110 阅读
相关 深入理解 Laravel Eloquent(一)——基本概念及用法 在本系列文章中,我将跟大家一起学习 Eloquent 的基本用法,探索 Eloquent 的各种高级功能,理解 Eloquent 背后的运行原理,并最终达到深入理解、灵活使用 「爱情、让人受尽委屈。」/ 2022年05月15日 16:44/ 0 赞/ 43 阅读
相关 Spring AOP用法(方法理解) Spring AOP用法(方法理解) 在使用AOP处理数据源的时候,对其方法进行了一顿所谓的复习。。。 直接上代码: @Aspect @Com Dear 丶/ 2022年05月21日 19:16/ 0 赞/ 51 阅读
相关 深入分析@Transactional的用法 [深入分析@Transactional的用法][Transactional] 文章主目录 [事务的基本概念][Link 1] [编程式事务与声明式事 比眉伴天荒/ 2022年06月09日 22:19/ 0 赞/ 169 阅读
相关 深入分析Mysql中limit的用法 原文出处:http://www.jb51.net/article/62851.htm 很久没用mysql的limit,一时大意竟然用错了,自认为(limit 开始,结束),其 阳光穿透心脏的1/2处/ 2022年06月11日 10:08/ 0 赞/ 42 阅读
相关 深入理解继承 继承(extends) 让类与类之间产生关系,子父类关系 继承的好处和弊端 A:继承的好处 a:提高了代码的 曾经终败给现在/ 2022年06月16日 11:09/ 0 赞/ 68 阅读
相关 MapReduce理解-深入理解MapReduce 前面的几篇博客主要介绍了[Hadoop][]的存储[HDFS][],接下来几篇博客主要介绍Hadoop的计算框架MapReduce。本片博客主要讲解MapReduce框架的具 心已赠人/ 2022年07月12日 14:59/ 0 赞/ 110 阅读
相关 深入理解extern用法 一、 extern做变量声明 l 声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。 我们一般把所有的全局变量和全局函数的实现都放在一个\.cpp 你的名字/ 2022年08月02日 12:48/ 0 赞/ 54 阅读
相关 mysql 索引深入理解_深入理解 mysql 索引 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。 我们知道,数据库查 迈不过友情╰/ 2022年10月25日 09:48/ 0 赞/ 88 阅读
还没有评论,来说两句吧...