并发容器
Queue
Queue用来临时保存一组等待处理的元素.。它提供了几种实现,包括:ConcurrentLinkedQueue,这是一个传统的 FIFO 队列,以及 PriorityQueue,这是一个(非并发的)优先队列。Queue 上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。
BlockingQueue
BlockingQueue扩展了 Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,直到队列中出现一个可用的元素。
ConcurrentHashMap
ConcurrentHashMap
与 HashMap 一样,ConcurrentHashMap也是一个基于散列表的 Map,但它使用了一种完全不同的枷锁策略来提供更高的并发性和伸缩性。
ConcurrentHashMap
并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种力度更细的加锁机制来实现更大程度的共享,这种机制叫分段锁
.。 – 作用:在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。
ConcurrentHashMap
与其他并发容器一起增强了同步容器类:它们提供的迭代器
不会抛出ConcurrentModificationExcetion
,因此不需要再迭代过程中对容器枷锁。
ConcurrentHashMap
返回的迭代器具有弱一致性。
在ConcurrentHashMap
中没有实现对 Map 加锁以提供独占访问
。在 Hashmap 和 synchronizedMap 中,获得 Map 的锁能防止其它线程访问这个 Map。
区别
与 HashTable 和 synchronizedMap 相比,大多数情况下 ConcurrentHashMap
代替同步 Map 能进一步提高代码的可伸缩行。
只有当应用程序需要加锁 Map 以进行独占访问
时,才应该放弃使用ConcurrentHashMap
。
迭代器弱一致性
ConcurrentHashMap
弱一致性的迭代器可以容忍并发的修改,当创建迭代器时会遍历已有元素,并可以(但不保证)在迭代器被构造后将修改操作反应给容器。
额外的原子 Map 操作
由于ConcurrentHashMap
不能被加锁来执行独占访问,因此我们无法使用客户端加锁来创建新的原子操作.。
翻译大白话,加锁后不能独占访问,客户端不能通过加锁创建新原子操作; 这里的独占访问等价于原子操作。
独占访问
官方翻译:“或者需要依赖于同步 Map 带来的一些其它作用。”
CopyOnWriteArrayList
CopyOnWriteArrayList
用于代替 List,在某些情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或者复制。(类似地,CopyOnWriteArrayList 的作用是替代同步 Set)
“写入时复刻(Copy-On-Write)”
容器的线程安全性在于,只要正确地发布一个 事实不可变的对象 , 那么在访问该对象时就不再需要进一步的同步。
“写入时复刻(Copy-On-Write)”
“写入时复刻(Copy-On-Write)” 容器在迭代器保留一个指向底层基础数组的引用,这个数组当前位于迭代器的起始位置,由于它不会被修改,因此对其进行同步时只需确保数组内容的可见性。
总结以上
有迭代需求(迭代器的迭代)、又是多线程同时进行、并且又需要同步的话,Map
使用 ConcurrentHashMap;List
使用CopyOnWriteArrayList;Set
使用 CopyOnWriteArraySet;大部分情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或者复制。
阻塞方法、中断方法
阻塞方法
当某方法抛出受检查异常(Checked Exception) InterruptedException 时,表示该方法是一个阻塞方法。
- Thread.sleep()
- BlockingQueue 的 put 和 take 等方法。
中断方法
Thread 提供 interrupt()
方法,用于中断线程或者查询线程是否已经被中断。每个线程都有一个布尔类型的属性,表示线程的中断状态,当终端线程时将设置这个状态。
Thread 提供 interrupt()
方法,中断是一种协作机制。一个线程不能强制其它线程停止正在执行的操作而去执行其它的操作。当线程 A 中断 B 时,A 仅仅是要求 B 在执行到某个可以暂停的地方停止正在执行其它的操作 – 前提是如果线程 B 愿意停止下来。
Thread 提供 interrupt()
方法,最长使用中断的情况就是取消某个操作。方法对中断请求的响应越高,就越容易及时取消那些执行时间很长的操作。
Thread 提供 interrupt()
方法,会有 2 中情况:
- 把 InterruptedExcetion 抛给调用者
- 有时候 InterruptedExcetion 不能抛出,则需要
恢复中断
,比如:当代码是 Runnable 的一部分时。在这些情况下就需要恢复中断。这样在调用栈中更高层的代码将看到引发了一个中断。 如下示例:
public class TaskRunnable implements Runnable{
BlockingQueue<Task> queue;
...
public void run(){
try{
processTask(queue.take()); // 移除并返回队列头部
} catch(InterruptedExcetion e){
// 恢复被中断的状态
Tread.currentThread().interrup();
}
}
}
同步工具
闭锁(Latch)
是一种同步工具类,可以延迟线程的进度直到其到达终止状态。
信号量(Semaphore)
用了控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。acquire 获取一个许可;release 释放一个许可。
栅栏(Barrier )
栅栏类似于闭锁,它能阻塞一组线程直到某个时间发生。
栅栏和闭锁关键区别:
闭锁:用于等待事件
栅栏:用于等待其他线程。栅栏用于实现一些协议,例如:“几个家庭决定在某个地方集合:所有人 6:00 在麦当劳碰头,到了以后要等其他人,之后再讨论下一步要做的事情。”
CyclicBarrier [ˈsaɪklɪk] [ˈbæriə(r)] 可以使一定数量的参与方反复地再栅栏位置汇集,它在并行迭代算法中非常有用。
第五章小节:
- 不可变对象一定是线程安全的。
- 不可变对象能极大地降低并发编程的复杂性。它们能更能为简单而安全,可以任意共享而无须使用加锁或保护性复制等机制。
- 用锁来保护每个可变变量
当保护同一个不变性条件中的的所有变量时,要使用同一个锁
。- 在执行复合操作期间,要持有锁。