一、一般模式下线程安全的HashMap
默认情况常用的HashMap都是线程不安全的,在多线程的环境下使用,常常会造成不可预知的,莫名其妙的错误。那么,我们如何实现一个线程安全的HashMap呢?其中一个可行的方式是使用Collectons.synchronizedMap() 方法来包装我们的HashMap。如下:
Map<String, String> map = Collections.synchronizedMap(new HashMap<String,String>());
Collections.synchronizedMap()会生成一个SynchronizedMap,它使用委托模式,将自己HashMap相关的功能交给传入HashMap实现,二自己负责线程安全的相关实现,下面看看
SynchronizedMap的定义:
private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map // 使用 mutex 实心对 map 的互斥操作 final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; }
如在代码中看到的,所有对Map的操作都需要用 这个 mutex 来同步,以实现线程安全。比如说下面这些常见的对HashMap的操作方法:
public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} }
除了以上看到的方法之外,其他的Map相关的方法有类似的操作。虽然这个包装的Map可以实现线程安全的要求,但是,它在多线程环境下的性能表现并不是很好,无论是对Map的读取还是写入,偶数需要获得 mutex 的同步锁,这会导致所有对Map的安全操作也会进入等待状态,知道mutex可用。 如果并发级别不高,那么这个 包装的Map可以基本满足要求,但是在搞并发的环境中,我们需要寻找新的解决方案。 ——---> 那就是我们的 ConcurrentHashMap.
二、提高"锁"性能的策略
1. 减少锁的持有时间
只在必要时进行同步,减少锁的持有时间。比如说在一个方法中只有一个变量需要同步,那么就没有必要对这整个方法都进行同步,而只需要同步这个变量即可。
// 无谓的加锁时间 public synchronied void syncMethod() { othrerMethod(); mutexMethod(); otherMethod(); } // 正确的加锁时间 public void syncMethod() { othrerMethod(); synchronied(this){ mutexMethod(); } otherMethod(); }
2. 减小锁的粒度
在获取全局信息方法不频繁的时候,通过减小锁的粒度可以搞系统的吞吐量。
3. 读写分离锁替换独占锁
在读都写少的情况下,使用读写分离锁,多线程读时不阻塞,而只对写线程进行同步。
4. 锁分离
对不同功能的锁进行不同的锁策略。
5. 锁粗化
系统对于"锁"的调度也是需要性能消耗的,又是我们可以适当的加大锁的范围,比如说在循环中尽量减少对锁的请求和释放,而是在得到锁的情况,一次性把问题解决。
原文地址:https://www.cnblogs.com/ytuan996/p/10585633.html