
小结
- Collection 接口的接口 对象的集合(单列集合)
- Map 接口:键值对的集合 (双列集合)
- Hashtable:同步, 线程安全
- HashMap:没有同步, 线程不安全
- LinkedHashMap:双向链表和哈希表实现
- WeakHashMap
- TreeMap:红黑树对所有的key进行排序
- IdentifyHashMap
1 | private static <T extends AbstractCollection> void setInfo(T s) {setInfo(s,null);} |
List
校验array和linked的区别
1 | private static <T extends AbstractList> T setList(T t, int num) { |
1 | public void testList() { |
1 | ArrayList:setList:总计耗时:629ms |
校验null值
1 | public void testList1() { |
1 | +======ArrayList======================= |
校验线程安全问题
1 | public void testSafe() { |
list总结
-
数据可重复,可为null
-
ArrayList和LinkedList区别- 两者都不保证线程安全
ArrayList查询快,增删慢LinkedList查询慢,增删快
-
上述代码中:为什么两者同样是插入
10_000_000条数据,LinkedList要比ArrayList慢这么多?这是因为
setList方法中用来添加元素的方式是尾插法, 参考可以了解到,在大数据量的情况下,ArrayList的扩容机制同LinkedList的Node机制要更有优势。 -
为什么线程不安全呢?
ArrayList不安全的原因:- 首先明确
ArrayList添加元素的操作是在add元素时,通过size下标进行写入集合,这一过程中用到了size++操作,这是无法保证原子性的操作,也就导致ArrayList在多线程环境下,可能会触发数组下标越界的错误ArrayIndexOutOfBoundsException,或者size同时被多个线程读取的问题。
LinkdedList不安全的原因:- 首先明确
LinkedList API中添加元素方法有多种,这里就用linkLast方法为例。该方法涉及变量尾指针``Nodelast ,下一个指针NodenewNode ,大小size,以及修改次数modCount。方法内size++和modCount++这两个操作无法保证原子性,导致在多线程环境下,存在多个同时获取相同的size和modCount`数值的线程,从而导致尾指针出现异常情况。
- 首先明确
-
如何保证线程安全呢?
1
2Collection<Integer> synchronizedCollection =
Collections.synchronizedCollection(new ArrayList<Integer>());1
CopyOnWriteArrayList<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
方式1:属于
java.util.Collections,通过synchronized实现线程安全。直接锁集合对象,性能不太行。1
2
3
4
5SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
public boolean add(E e) {synchronized (mutex) {return c.add(e);}}方式2:属于
java.util.concurrent.CopyOnWriteArrayList,诸如add,remove,set中存在着copyOf操作,导致该api在处理大数据量的时候,性能表现极差。下面是源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取当前元素集
Object[] elements = getArray();
// 获取长度
int len = elements.length;
// 拷贝一份容量比原先大1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将元素加入到新元素集合
newElements[len] = e;
// 将新元素集合写到list中
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}两者都没有继承
AbstractList
Set
校验特性
1 | TreeSet<Integer> treeSet = new TreeSet<>(); |
1 | +======TreeSet======================= |
校验线程安全
1 | private static <T extends AbstractSet> void inThread(T t, int tNum, int eNum) { |
1 | public void testSetSafe() { |
结果
1 | java.lang.NullPointerException |
set总结
-
保证数据是唯一的
-
TreeSet,HashSet,LinkedHashSet三者的区别TreeSet不能存储null数据HashSet和LinkedHashSet可以存储null数据TreeSet可以保证数据有序HashSet可以保证重写hashcode的数据类型有序LinkedHashSet可以保留插入顺序TreeSet底层是二叉树HashSet底层是hashLinkedHashSet底层是链表hash
-
TreeSet调了一个NavigableMap,其插入操作会去调用TreeMap中的put方法。多线程中,在调用put时,可能遇到多个线程在添加同一个元素的情况出现,这使compare的结果一致,从而触发插入多个同一元素的情况。1
2
3
4
5
6
7
8
9
10
11private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
} -
HashSet内部调了个HashMap,其插入操作会去调用HashMap中的put方法。多线程中,在调用put时,可能遇到多个线程在添加同一个元素的情况出现,这使对比hash的结果一致,从而触发插入多个同一元素的情况。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
} -
LinkedHashSet是链表结构的HashSet,调的都是HashMap。
Map
校验特性
Hashtable
1 | Hashtable<Integer, Integer> hashtable = new Hashtable<>(); |
结果:
-
key和value都不能为空 -
同
key覆盖 -
不建议使用HashTable,Oracle官方也将其废弃,建议在多线程环境下使用ConcurrentHashMap类。
-
线程安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29Hashtable<Integer, Integer> hashtable = new Hashtable<>();
inThread(hashtable,3,1000);
private static <T extends Dictionary> void inThread(T t, int tNum, int eNum) {
Thread thread = new Thread(() -> {
for (int i = 0; i < tNum; i++) {
Thread thread1 = new Thread(() -> setMap(t, eNum));
thread1.start();
}
});
thread.start();
try {
thread.join();
Thread.sleep(eNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
String.format("在%1$s个线程,每线程添加%2$s个元素的情况下,%3$s中元素数量是:%4$s",
tNum, eNum, t.getClass().getTypeName().split("\\.")[2], t.size()));
}
private static <T extends Dictionary> T setMap(T t, int num) {
for (int i = 0; i < num; i++) {
t.put(i, i);
}
return t;
}
// 在3个线程,每线程添加1000个元素的情况下,Hashtable中元素数量是:1000
HashMap
1 | HashMap<Integer, Integer> hashMap = new HashMap<>(); |
结果:
- 允许
key和value为null - 线程不安全
LinkedMap
1 | LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap<>(); |
结果:
-
源码,其继承自
HashMap1
public class LinkedHashMap<K,V> extends HashMap<K,V>
-
线程不安全
TreeMap
1 | TreeMap<Integer, Integer> treeMap = new TreeMap<>(); |
结果:
- 不允许
key为null,value可以为null - 同
key覆盖 - 线程不安全
ConcurrentHashMap
1 | ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<>(); |
结果:
- 属于
java.util.concurrent,但继承了AbstractMap - 不允许
key,value为null - 线程安全
检验线程安全代码
1 | private static <T extends AbstractMap> void inThread(T t, int tNum, int eNum) { |
java.util.concurrent
集合类在java.util,为解决并发问题,提供了java.util.concurrent解决方案。比如常用的HashMap并发解决方案的ConcurrentHashMap就在这个包下。更多内容多线程-concurrent中介绍。
Collections
对collection操作的工具类。
摘要几个常用的API
| 方法 | 描述 |
|---|---|
sort(List<T> list [, Comparator<? super T> c]) |
对list进行排序;重载一个比较器版本 |
synchronizedCollection(Collection<T> c) synchronizedList(List<T> list)synchronizedMap(Map<K,V> map)synchronizedSet(Set<T> set) |
提供并发解决方案 |
min/max (Collection<T> c [, Comparator<? super T> c]) |
获取最小/最大值 |
reverse(List<T> list) |
反转列表 |
replaceAll(List<T> list,T oldVal,T newVal) |
替换列表中元素 |
swap(List<T> list,int i,int j) |
列表中元素交换位置 |
copy(List<? super T> dest, List<? extends T> src) |
src往dest拷贝,确保src.size比dest.size小 |