文章目录
-
一、同步容器
-
1.1、同步容器类介绍
* 1.2、常用的同步容器类
* 1.3、使用同步容器类的方式
* 1.4、同步容器类的实现机制
* 1.5、使用同步容器类的弊端1
21 * 二、并发容器
2 -
2.1、什么是并发容器类
* 2.2、CAS算法
* 2.3、ConcurrentHashMap
* 2.4、CopyOnWriteArrayList
Java平台的类库包含了一个并发构造块的丰富集合,本篇将介绍在并发编程中十分常用的两种容器类,一是同步容器,二是并发容器,它们在某些特定的场合中可以用来替代我们常用的集合类,比如在高并发和多线程请求的情况下。
本篇总结自《Java并发编程实践》第五章 构造块 章节的内容,详情可以阅读该书。
一、同步容器
1.1、同步容器类介绍
同步容器类包括两部分,一个是Vector和Hashtable,它们是早期JDK的一部分;另一个是它们的同系容器,在JDK1.2之后才被 引入的同步包装类这些类是由Collections.synchronizedXxx工厂方法创建的。这些类通过封装它们的状态,并对每个公共方法进行了同步而实现了线程安全,这样一次只有一个线程能访问容器的状态。
1.2、常用的同步容器类
常用的同步容器类有Collections.synchronizedMap、Collections.synchronizedList、Collections.synchronizedSet、Collections.synchronizedSortSet、Collections.synchronizedSortMap,它们分别是Map、List、Set、SortSet、SortMap的同步容器类。
1.3、使用同步容器类的方式
使用同步容器类的方式很简单,如下代码就是使用了一个Map的同步类,对HashMap进行了包装。
1
2
3
4 1//创建一个同步Map容器类,包装HashMap,使其线程安全
2Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<String, String>());
3
4
接下来的操作和平时使用HashMap没有什么区别,常用的方法都没有改变:
1
2
3
4
5
6
7
8
9
10
11 1 //存储key-value
2 synchronizedMap.put("hello","world");
3
4 //通过key读取value
5 String hello = synchronizedMap.get("hello");
6 System.out.println(hello);
7
8 //判断key存在不存在,不存在则put
9 synchronizedMap.putIfAbsent("arong","java");
10
11
1.4、同步容器类的实现机制
同步容器类的实现机制前面已经说过,其实就是依靠synchronized块对方法进行了同步,以阻止多个线程对一个方法进行并发的访问,从而使其达到线程安全的效果,以下是Collections.synchronizedMap的相关代码,我们可以看出,每个方法都被上锁了:
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 1//static静态化使用户通过Collections类可直接获取到它
2public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
3
4 //这一步返回一个SynchronizedMap类,并将Map作为参数传入
5 return new SynchronizedMap<>(m);
6 }
7
8 //真正的同步容器类
9 private static class SynchronizedMap<K,V>
10 ...
11 //所有的方法中都使用了synchronized对有状态变量进行了同步
12 public int size() {
13 synchronized (mutex) {return m.size();}
14 }
15 public boolean isEmpty() {
16 synchronized (mutex) {return m.isEmpty();}
17 }
18 public boolean containsKey(Object key) {
19 synchronized (mutex) {return m.containsKey(key);}
20 }
21 public boolean containsValue(Object value) {
22 synchronized (mutex) {return m.containsValue(value);}
23 }
24 public V get(Object key) {
25 synchronized (mutex) {return m.get(key);}
26 }
27
28 public V put(K key, V value) {
29 synchronized (mutex) {return m.put(key, value);}
30 }
31 public V remove(Object key) {
32 synchronized (mutex) {return m.remove(key);}
33 }
34 public void putAll(Map<? extends K, ? extends V> map) {
35 synchronized (mutex) {m.putAll(map);}
36 }
37 public void clear() {
38 synchronized (mutex) {m.clear();}
39 }
40
41 ...
42
43
1.5、使用同步容器类的弊端
同步容器类虽然能保证在多线程请求的情况下容器数据的安全,但是弊端也是比较明显的:所有的方法都是同步的,每次只能有一个线程获得锁,其他线程全部阻塞,这样会导致性能问题,所以说,在对性能的要求不高,但是要求安全性良好的情况下,可以使用同步容器类,但是如果希望容器在并发请求时能同时接受多个线程的处理,并且也能保证线程安全的话,推荐使用下文介绍的并发容器类。
二、并发容器
2.1、什么是并发容器类
Java5.0通过提供几个并发的容器类来改进同步容器。同步容器通过对容器的所有状态进行串行访问。从而实现了它们的线程安全。这样做的代价是削减了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。另一方面,并发容器是为了多线程并发访问而设计的。Java5.0添加了ConcurentHashMap来替代同步的HashMap实现;当多数操作为读取操作时,CopyOnWriteArrayList是List相应的同步实现。新的ConcurrentMap接口还加入了常见复合操作,比如“缺少即加入(put-if-absent)”、替换和条件删除等。并发容器类位于java.util.concurrent,即并发包下。
2.2、CAS算法
CAS是一种无锁的非阻塞算法,全称为:Compare-and-swap(比较并交换),大致思路是:先比较目标对象现值是否和旧值一致,如果一致,则更新对象为新值;如果不一致,则表明对象已经被其他线程修改,直接返回。
2.3、ConcurrentHashMap
ConcurrentHashMap实现了HashTable的所有功能,线程安全,但却在检索元素时不需要锁定,因此效率更高。ConcurrentHashMap的key 和 value都不允许null出现。原因在于ConcurrentHashMap不能区分出value是null还是没有map上,相对的HashMap却可以允许null值,在于其使用在单线程环境下,可以使用containKey(key)方法提前判定是否能map上,从而区分这两种情况,但是ConcurrentHashMap在多线程使用上下文中则不能这么判定。在并发编程中,常用来替换Collections.synchronizeHashMap。
2.4、CopyOnWriteArrayList
CopyOnWriteArrayList提供高效地读取操作,使用在读多写少的场景。CopyOnWriteArrayList读取操作不用加锁,且是安全的;写操作时,先copy一份原有数据数组,再对复制数据进行写入操作,最后将复制数据替换原有数据,从而保证写操作不影响读操作。在并发编程中,常常用来替换Collections.synchronizedList。