线程不安全的HashMap
因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。
因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。
如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁。
那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。
首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。
Segment是一种重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,
HashEntry则用于存储键值对数据。
一个ConcurrentHashMap里包含一个Segment数组, 每个Segment守护者一个HashEntry数组里的元素,
当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
ConcurrentHashMap使用方法:
虽然ConcurrentHashMap是线程安全的,如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,如果有另外一个线程调用了put(name, x),你再去执行put(name,x),就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,你还是有可能违反原子操作的规则。
当然可以用 锁 解决这个问题,但性能受到很大影响。
另一种思路,也可以使用ConcurrentMap定义的循环CAS方法:
- V putIfAbsent(K key, V value)
- 如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value。
- boolean remove(Object key, Object value)
- 如果key对应的值是value,则移除K-V,返回true。否则不移除,返回false。
- boolean replace(K key, V oldValue, V newValue) //循环CAS算法
- 如果key对应的当前值是oldValue,则替换为newValue,返回true。否则不替换,返回false。
- public static void demo1() {
- final Map<String, Integer> count = new ConcurrentHashMap<>();
- Runnable task = new Runnable() {
- @Override
- public void run() {
- Integer oldValue, newValue;
- for (int i = 0; i < 5; i++) {
- while (true) {
- oldValue = count.get("a");
- if (null == oldValue) {
- newValue = 1;
- if (count.putIfAbsent("a", newValue) == null) {
- break;
- }
- } else {
- newValue = oldValue + 1;
- if (count.replace("a", oldValue, newValue)) {
- break;
- }
- }
- }
- }
- }
- };
- new Thread(task).start();
- new Thread(task).start();
- }
还一种思路,也可以使用Atomic原子操作方法(本质也是循环CAS):
- public static void demo1() {
- final Map<String, AtomicInteger> count = new ConcurrentHashMap<>();
- Runnable task = new Runnable() {
- @Override
- public void run() {
- AtomicInteger oldValue;
- for (int i = 0; i < 5; i++) {
- oldValue = count.get("a");
- if (null == oldValue) {
- AtomicInteger zeroValue = new AtomicInteger(0);
- oldValue = count.putIfAbsent("a", zeroValue); //赋予初始值:0
- if (null == oldValue) {
- oldValue = zeroValue;
- }
- }
- oldValue.incrementAndGet(); //原子循环CAS自增操作
- }
- }
- };
- new Thread(task).start();
- new Thread(task).start();