装饰器 Decorator
装饰器模式可以用来实现线程安全,其基本思想是为非线程安全对象创建一个相应的线程安全的包装类对象。包装对象与相应的非线程安全对象具有相同的接口,包装对象通常会借助锁。
java.util.Collections.synchronizedX,可以反会线程安全的集合,X 可以为 Map、Set、List。这些对象称为同步集合。同步对象的 Iterator 不是线程安全的,为了保障对同步集合遍历操作的线程安全性,
并发集合
juc 包引入了线程安全的集合对象,通常情况下可以作为同步集合的替代品。
| 非线程安全对象 | 线程安全对象 | 共同接口 | 遍历方式 |
|---|---|---|---|
| ArrayList | CopyOnWriteArrayList | List | 快照 |
| HashSet | CopyOnWriteArraySet | Set | 快照 |
| LinkedList | ConcurrentLinkedQueue | Queue | 准实时 |
| HashMap | ConcurrentHashMap | Map | 准实时 |
| TreeMap | ConcurrentSkipListMap | StoredMap | 准实时 |
| TreeSet | ConcurrentSkipListSet | SortedSet | 准实时 |
并发集合自身就能对其进行线程安全遍历,无需加锁。并且,对并发集合的遍历操作和对其进行的更新操作是可以由不同的线程并发执行的。并发集合在内部保障线程安全时通常不借助锁,而是用CAS操作,或对锁进行优化(粒度极小的锁)。并发集合的吞吐率要高于同步集合,同步集合会导致锁争用,从而导致上下文切换开销大。
实现线程安全的遍历通常有两种方式:快照和准实时。快照是在Iterator示例被创建的那一刻,待遍历结构内部生成的只读副本,它反映了集合某一时刻的状态。不同线程对集合进行遍历会得到不同的副本,所以无需加锁就可以实现线程安全。这种遍历返回的iterator是不支持remove的。缺点是集合比较大时,创建副本的开销会比较大。CopyOnWrite就是使用这种方式。
准实时遍历操作不针对副本,但又不借助锁来保障线程安全,从而使得遍历操作与更新操作并发进行。遍历过程中其它线程对集合进行修改,也能在遍历中反映出来。这种形式返回的Iterator支持remove操作。Concurrent集合采用这种方式。由于Iterator是被设计成一次只能有一个线程使用,所以如果多个线程要进行遍历操作,这些线程间不适宜共享一个Iterator。
ConcurrentLinkedQueue是Queue接口的线程安全的实现类,相当于LinkedList的线程安全版。其内部不使用锁,而是用CAS,因此是非阻塞的,使用时不会导致线程被暂停。ConcrrentLinkedQueue遍历是准实时的。与BlockingQueue相比,更适合更新操作和遍历操作并发的场景而BlockingQueue更适合多个线程并发更新同一队列的场景,比如消费者生产者模式。