先看一段代码: public class Locale { private final static Map 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 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 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)。本文仅代表作者个人观点,不代表守望者的本意。如有违法侵权内容,请提交到守望者管理员处,立即处理。 |
行业聚焦 面试交流 职位推荐 开发视频 技术交流 腾讯微博 新浪微博
友情链接:课课家教育 阿里云 鲜果 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