Java并发编程学习之ConcurrentHashMap如何保证线程安全
前言
-
大佬语录
并发不止并发写冲突,读写也有冲突。并发写有几种解决办法:
- 单线程话(a.直接上锁,b.借助队列+异步单线程,如果需要知道结果可能需要回掉等机制)
- 双重检测
- 借助于volatile,解决写和读的冲突
- 借助于其他无锁原子术语
并发和事务是解决同样的问题:原子性,一致性,隔离性,持久性(也叫可见)。
乐观锁的思想:就是第一把自己看见的东西保存下来,做完处理以后,然后去原子更新(putIfAbsent,replace),更新成功怎么处理,更新失败(就是别人也在更新)怎么处理(重试,或者返回结果)。
cas 也是类似的思路,volatile只解决了可见性,它使用的场景,就是单线程写,多线程读的场景
-
怎么解决并发更新Map集合的问题?
单个操作安全并不代表整体安全
putIfAbsent
-
内容介绍
java 5.0 引入了 ConcurrentMap 接口,在这个接口里面 put-if-absent 操作以原子性方法 putIfAbsent(K key, V value) 的形式存在
-
方法说明
V putIfAbsent(K key, V value)
如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value。
-
适用场景
Map中新增key和value。
-
错误示例
使用了 Map 的 concurrent 形式(ConcurrentMap、ConcurrentHashMap),并简单的使用了语句map.putIfAbsent(key, locale) 。这同样不能保证相同的 key 返回同一个 Locale 对象引用。不要忽视了 putIfAbsent 方法是有返回值的,并且返回值很重要
public class Locale implements Cloneable, Serializable { private final static ConcurrentMap<String, Locale> map = new ConcurrentHashMap<String, Locale>(); 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; } // .... }
假设A线程map.get(key)获取为null,则构造新的Locale对象LA,这时由于并发操作,另外个线程B也构造了新的Locale对象LB,然后A先调用putIfAbsent将LA存入map并返回个null,A线程此时获取的是LA正确,但是线程B调用putIfAbsent将LB存入map发现key-LA已经存在,则不进行put操作,返回值为LA,但是实际return locale返回的却是LB,返回不是同一个 Locale 对象引用。
“如果(调用该方法时)key-value 已经存在,则不put并返回那个 value 值。如果调用时 map 里没有找到 key 的 mapping,返回一个 null 值”
-
正确示例
public final class Locale implements Cloneable, Serializable { // cache to store singleton Locales private final static ConcurrentHashMap<String, Locale> cache = new ConcurrentHashMap<String, Locale>(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; } // .... }
remove
-
方法说明
boolean remove(Object key, Object value)
如果key对应的值是value,则移除K-V,返回true。否则不移除,返回false。
-
使用场景
删除Map中key和value
replace
-
方法说明
boolean replace(K key, V oldValue, V newValue)
如果key对应的当前值是oldValue,则替换为newValue,返回true。否则不替换,返回false。
-
适用场景
更新Map中的key对应的value
参考链接
-
深入理解ConcurrentMap.putIfAbsent(key,value) 用法
https://blog.youkuaiyun.com/exceptional_derek/article/details/40384659
-
ConcurrentHashMap中的remove方法的bug
https://blog.youkuaiyun.com/killalllsd/article/details/83607945
-
Java ConcurrentHashMap多线程下的正确使用
https://blog.youkuaiyun.com/guandongsheng110/article/details/80844574
-
深入理解Java枚举
https://www.cnblogs.com/ziph/p/13068923.html