Java并发编程学习之ConcurrentHashMap如何保证线程安全
- 前言
- putIfAbsent
- remove
- replace
- 参考链接
前言
- 大佬语录
并发不止并发写冲突,读写也有冲突。并发写有几种解决办法:
- 单线程话(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) 用法
- ConcurrentHashMap中的remove方法的bug
- Java ConcurrentHashMap多线程下的正确使用
- 深入理解Java枚举