Collection 笔记

判断线程安全类的准则

当需要在多线程环境下,使用一些类时,我们首先就需要判断当前类是否是线程安全的,否则出现线程安全问题。

准则一

最直接,方便的准则就是查看类的源代码注释,上面提到的集合类都在注释声明了该类不是线程安全的。查找注释的时候可以直接搜索 Thread或者 synchronize关键字。

准则二

查看有没有使用同步语句:java.util.concurrent.* / synchronize / Object.wait()

如果以上准则都不满足,几乎可以断定不是线程安全的类。

多线程环境下使用 Collection

多线程换环境下使用 ArrayList

  • 最好的办法是使用 Collections.sysnchronized(new ArrayList())
  • 慎用 CopyOnWriteArrayList,性能问题

多线程换环境下使用 HashSet / HashMap

  • HashMap 无脑使用 ConcurrentHashMap 替换
  • HashSet 没有对应的 ConcurrentHashSet类,可使用以下方法
    • Collections._newSetFromMap_(new ConcurrentHashMap<>()); 从一个 ConcurrentHashMap 获取 HashSet
    • Guava 提供的 Sets._newConcurrentHashSet_();方法,创建一个线程安全的 Set。

多线程换环境下使用 TreeSet / TreeMap

  • 使用 ConcurrentSkipListSetConcurrentSkipListMap (很少使用到),都是基于 Skip List 这一数据结构实现的。

多线程环境下新的 Collection 类

  • BlockingQueue 可以等待的队列。
  • 当队列为空的时候,获取元素就会进入等待状态,直到队列中有元素。插入元素时相反。
  • 使用场景很高级,很少使用到。一旦使用上,就要格外的小心。
  • 常用的实现 ArrayBlockingQueue,SynchronousQueue。

Collection 工具方法集合

返回一个空集合

1
2
3
4
Collections.emptyList();
Collections.emptyMap();
Collections.emptySet();
...

emptySet,emptyMap,emptyList,返回一个空集合。该空集合是全局唯一的,并且自动泛型化,在方法返回的时候很适用,节省时间和内存。

将集合变成线程安全的

1
2
3
4
5
6
7
List unSafeList = new ArrayList();
Set unSafeSet = new HashSet();
Map unSafeMap = new HashMap();

Collections.synchronizedList(unSafeList);
Collections.synchronizedMap(unSafeMap);
Collections.synchronizedSet(unSafeSet);

synchronizedList,synchronizedMap,synchronizedSet 把制定的集合变成线程安全的。实际上只是把对应的集合中的方法加锁,优先使用 Concurret 集合类。

将集合变成不可变的(只读)

1
2
3
4
5
6
7
List modifiableList = new ArrayList();
Set modifiableSet = new HashSet();
Map modifiableMap = new HashMap();

Collections.unmodifiableList(modifiableList);
Collections.unmodifiableSet(modifiableSet);
Collections.unmodifiableMap(modifiableMap);

unmodifiableList,unmodifiableSet,unmodifiableMap 把指定的集合变成不可变的集合(也可以使用 Guava 的 Immutable )。

Queue 与 Deque

Queue 是数据结构中的经典的「队列」,一个方向进,一个方向出。

Deque 是「双端队列」,两个方向都可以进出。

Queue 和 Deque 的常用实现是 LinkedList。

Vector 和 Stack

Vector 就是 ArrayList 的前生。

Stack 是数据结构中经典的「栈」,使用它的好处就是能够让人一眼看出你的意图。

**Vector 和 Stack 都是 JDK 中非常古老的 class,并不是 interface! **两个类都不推荐使用,前者可以用 ArrayList 代替,后者可用 Deque 代替。

PriorityQueue

优先级队列,根据优先级进行排列。基于数据结构二叉堆(特殊的二叉树)实现,根节点的元素比所有子节点的元素都小。因此可以快速获取最大和最小元素。

PriorityQueue 的实现必须提供比较器,在初始化队列的时候提供,或者由包含的元素提供。如果两个都不提供比较器,则报错。该优先级队列非线程安全。

与 TreeSet 区别:数据结构不同,检索速度非常快(复杂度 O(1)),移除元素调整比 TreeSet 更快,遍历不保证有序。

Guava 对 Collection 体系的有力扩展

快速创建一个集合

1
2
Sets.newHashSet(1, 2, 3, 4, 5, 6);
Lists.newArrayList(1, 2, 3, 4, 5, 6);

Lists / Sets / Maps,方便的工具方法 newArrayList() / newLinkedList() / newHashSet() / newHashMap() 等,快速创建一个集合。

快速创建一个不可变的 Map

1
2
3
4
5
6
7
8
ImmutableMap.of("key1", 1, "key2", 2);
ImmutableMap.builder()
.put("key1", 1)
.put("key2", 2)
.put("key3", 3)
.put("key4", 4)
.put("key5", 5)
.build();

ImmutableMap 快速创建一个不可变的 Map,如果 Map 中的元素较少,可以使用 of 方法,元素较多使用 builder 方法。

创建特殊的 Set 和 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Multiset multiset = HashMultiset.create();
multiset.add("zz");
multiset.add("zz");
multiset.add(1);
multiset.add(2);
System.out.println(multiset);

// 打印结果
[zz x 2, 1, 2]


Multimap multimap = HashMultimap.create();
multimap.put(2,"z");
multimap.put(1,"a");
multimap.put(1,"b");
multimap.put(1,"c");
System.out.println(multimap);

// 打印结果
{1=[a, b, c], 2=[z]}

Multiset 可以记录重复插入元素的次数,MultiMap 支持同一个键插入多个元素。

BiMap

1
2
3
4
5
6
7
8
9
10
BiMap<String, Integer> map = HashBiMap.create();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
System.out.println(map.get("a"));
System.out.println(map.inverse().get(3));

// 打印结果
1
c

BiMap 是一个双向 Map,既可以键映射到值,也支持值映射到键。


Collection 笔记
http://wszzf.top/2021/08/18/Collection 不常见的重要实现和原理/
作者
Greek
发布于
2021年8月18日
许可协议