Java并发编程学习之ConcurrentHashMap如何保证线程安全

  • 前言
  • putIfAbsent
  • remove
  • replace
  • 参考链接


前言

  • 大佬语录
    并发不止并发写冲突,读写也有冲突。并发写有几种解决办法:
  1. 单线程话(a.直接上锁,b.借助队列+异步单线程,如果需要知道结果可能需要回掉等机制)
  2. 双重检测
  3. 借助于volatile,解决写和读的冲突
  4. 借助于其他无锁原子术语

并发和事务是解决同样的问题:原子性,一致性,隔离性,持久性(也叫可见)。

乐观锁的思想:就是第一把自己看见的东西保存下来,做完处理以后,然后去原子更新(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枚举