HashMap和ConcurrentHashMap
首先我们先来聊聊集合框架!
一、集合框架
1、集合框架的关系
(1)Iterable、Collection、List、Set、Map是最基本的
(2)Queue(队列)、Deque(双端队列)、Stack(栈)
2、集合框架的具体实现类
(1)非线程安全的实现类:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap
(2)同步的实现类:Vector、HashTable
(3)并发访问安全的实现类:CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap(重点)
3、每个实现类的底层数据结构
ArrayList(数组)
LinkedList(链表)
HashMap(jdk1.7是数组+链表,jdk1.8是数组+链表+红黑树)
HashSet(基于HashMap实现,键就是元素,值就是new Object)
TreeMap(红黑树)
4、判断是否是同一个对象
值比较:equals()来进行判断。要求根据业务重写hashCode()和equals()。
5、迭代器
- Iterable接口下的实现类,都可以使用迭代器
- for循环操作底层也是使用的迭代器
- fail-fast(快速失败的迭代器):非线程安全的类使用的迭代器(在遍历操作的时候们只能使用迭代器修改元素,不使用迭代器修改元素,就会抛出异常),比如下面这段代码,test1()没使用迭代器进行修改元素,test2()使用迭代器进行修改,观察:
import java.util.*;
public class Main{
static List<String> LIST = new ArrayList<>();
static {
LIST.add("A");
LIST.add("B");
LIST.add("C");
LIST.add("C");
LIST.add("D");
}
public static void test1(){
int i = 0;
for(String str : LIST){
//遍历过程中,使用非迭代器的方式修改元素是不允许
if(i==2)
LIST.remove(2);
else
System.out.println(str);
i++;
}
//遍历操作外修改是允许的
//LIST.remove(2);
}
public static void test2() {
int i = 0;
Iterator<String> it = LIST.iterator();
while(it.hasNext()){
String str = it.next();
if(i==2)
it.remove();
// LIST.remove(2);
else
System.out.println(str);
i++;
}
}
public static void main(String[] args) {
// test1();
test2();
}
}
- fail-saft(安全失败的迭代器):CopyOnWriterArrayList、ConcurrentHashMap本身没有迭代器,但内部的属性(Entry、Node)具有迭代器
- HashMap内部的迭代器是fail-fast类型,ConcurrrentHashMap内部的迭代器是fail-saft类型。fail-saft支持在遍历操作时,修改元素
二、HashMap
前面的博客已经细致的讲解过了HashMap,没看过的小伙伴可以去看看。哈希表以及HashMap代码实现
如果 负载因子(size/数组长度)> 0.75,需要对数组进行扩容
hash冲突的问题:JDK实现是链地址法解决
使用场景:查询频率高,插入/删除频率低
可能产生的问题:环形链表(多个线程操作HashMap所导致的)
因为HashMap添加元素分为两步:1、添加到头结点
2、调换链表的位置
1、HashMap JDK1.7的实现
2、HashMap JDK1.8的实现
JDK1.7:
多线程下的数据操作是不安全的,并且可能出现环形链表造成死循环(扩容时resize操作会改变链表中元素的顺序,从头部插入,多线程执行顺序的不确定性可能出现线程1将node1.next指向node2,而接着执行线程2将node2.next指向node1)
JDK1.8:
尽管jdk1.8修复了死循环问题,只有可能出现数据丢失的情况。多线程下还是建议使ConcurrrentHashMap
3、HashTable
- 底层数据结构:数组+链表
- 底层实现再put、get,内部大部分的方法都是使用synchronized修饰的
- 多线程操作下是安全的,但是效率太低了
- HashTable不能null键,null值
三、ConcurrentHashMap
1、底层数据结构以及原理
(1)jdk1.7是数组+链表,实现原理是Segment分段锁+ReentrantLock锁机制(segment数组+HashEntry数组+链表)
(2)jdk1.8是数组+链表+红黑树,实现原理基于CAS+synchronized机制
2、特性:
(1)不是把Map整个对象加锁,而是缩小了锁的范围(1.7是segment,1.8是node)
(2)可以在多线程环境下并发、并行执行不同segment/node的插入/删除操作。读操作无锁的,使用volatile关键字,保证线程安全
(3)1.7对同一个segment的操作是ReentrantLock.lock()/unlock()进行线程间的同步,1.8是Node的操作,如果node==null,适应CAS进行自旋尝试插入,如果node != null,synchronized(node)加锁,达到线程间安全。