J.U.C
是JDK提供的一个包名的缩写,全名为:java.util.concurrent
如图:
1.ArrayList --> CopyOnWriteArrayList
相比于ArrayList,它是线程安全的。
当有新元素添加到CopyOnWriteArrayList容器中时,它先从原有数组中拷贝一份出来,在新的数组中做写操作,写完之后将原来的数组指向到新的数组。它的整个Add操作都是在锁的保护下进行的。读的操作是不需要加锁的,写操作是加锁的。
缺点:写操作时需要复制数组,消耗内存。如果元素内容过多,可能会导致Full gc。不能用于实时读的场景。
适用场景:读多写少。
设计思想:读写分离;最终一致性;使用时另外开辟空间,通过这种方式解决掉并发冲突。
2.HashSet --> CopyOnWriteArraySet TreeSet --> ConcurrentSkipListSet
CopyOnWriteArraySet是基于CopyOnWriteArrayList的,适用场景跟CopyOnWriteArrayList基本一样。
ConcurrentSkipListSet是基于Map集合的,在多线程环境下他的contains,remove,add都是线程安全的。但是批量操作的那些,例如containsAll,removeAll,addAll这些,不保证是线程安全的。它不能有空元素。
3.HashMap --> ConcurrentHashMap TreeMap--> ConcurrentSkipListMap
(重点)
一.Hashmap
1.Hashmap的数据结构:
HashMap的底层就是一个数组结构,而数组中的每一项又是一个链表结构;
当我们新建一个HashMap的时候,就会初始化一个数组出来。
HashMap有两个参数影响它的性能,一个是初始容量(默认是16),一个是加载因子(默认是0.75)。
容量是哈希表中桶的数量。初始容量只是哈希表在创建时的容量;
加载因子是哈希表在容量自动增加之前,可以达到多满的一个尺度。如果达到了加载因子的值 ,那么会调用resize方法进行扩容。将容量进行翻倍。
这两个值在初始化时构造函数是可以自定义的。
2.Hashmap的寻址方式:
对于一个新插入的数据,或者我们需要读取的数据。Hashmap会对它的key按照一定的计算规则计算出的哈希值并对数组长度进行取模。结果作为它数组中的index。但是在计算机中取模的代价比较大,所以Hashmap要求数组的长度必须为2的N次方,此时呢它将key的哈希值对2的N-1次方进行与运算,结果与取模相同的。
Hashmap并不要求用户在初始化的时候指定容量必须传入的N次方的整数,而是在初始化时根据传入的参数计算出一个满足的容量值。源码中的tableSizeFor方法即可看到。
总所周知,Hashmap的线程不安全,其实主要体现在刚才的resize方法可能会出现死循环,以及使用迭代器会出现FastFail。
当Hashmap得size超过容量乘加载因子的时候,就会进行扩容。就是创建了一个新的长度为原容量2倍的数组。并将原数组全部重新插入到现数组中,这个方法我们成为rehash。这个方法不保证线程安全,并且在多线程环境下可能会出现死循环。
二.ConcurrentHashMap
1.ConcurrentHashMap的数据结构:
如图:线程安全的
也是数组+链表结构,但是最外层不像HashMap直接是个大的数组,而是Segment数组,每个Segment里面是跟HashMap结构差不多的链表数组
Java7之前是分段锁。Java8之后在链表长度超过一定长度之后(默认是8),链表转化为了红黑树。提高并发性。
三.ConcurrentSkipListMap
ConcurrentSkipListMap的效率一般没有ConcurrentHashMap高,但是也有优点。
1.可以保证有序性
2.并发数越大,越能体现优势。