您的位置:首页 > 编程语言 > Java开发

《JAVA并发编程实践》学习笔记(第四.五章)

2016-04-08 04:02 429 查看
第四章 组合对象
4.1 设计线程安全的类

设计线程安全类的过程应包括下面3个基本要素:

1. 确定对象状态是由哪些变量构成的;

2. 确定限制状态变量的不变约束;

3. 指定一个管理并发访问对象状态的策略;

同步策略(synchronization policy)定义了对象如何协调对其状态的访问,并不会违反它的不变约束或者后验条件。

4.1.1 收集同步需求

维护类的线程安全性以为着要确保在并发访问的情况下,保护它的不变约束;这需要对其状态进行判断。对象与变量拥有一个状态空间(state space):即他们可能处于的状态范围。

如果一个操作的过程中可能出现非法状态转换,则该操作必须是原子。

一个类的不变约束也可以约束多个状态变量。

不理解对象的不变约束和后验条件,你就不能保证线程安全性。要约束状态变量的有效值后者状态转换,就需要原子性与封装性。

4.1.2状态依赖的操作

4.1.3 状态所有权

所有权(Ownership)并不是语言中明确具化的概念,而是类设计中的元素。

在很多情况下,所有权与封装性总是在一起出现的:对象封装它拥有的状态,且拥有它封装的状态

4.2 实例限制

通过使用实例限制(instanceconfinement),封装简化了类的线程安全工作,这通常称为“限制”。

将数据封装在对象内部,把对数据的访问限制在对象的方法上,更容易确保线程在访问数据时总能获得正确的锁。

实例限制是构建线程安全的最简单的方式之一。它还使得对锁策略的选择更富有灵活性;实例限制还允许不同的锁保护不同的状态变量。

只要包装器对象占有着对下层容器唯一的可触及的引用,包装器对象就是线程安全的。

限制性使构造线程安全的类变得更容易。因为类的状态被限制后,分析它的线程安全性时,就不必检查完整的程序。

4.3 委托线程安全

几乎所以对象都是组合对象。

4.3.2非状态依赖变量

如果一个类由多个彼此独立的线程安全的状态变量组成,并且类的操作不包含任何无效状态转换时,可以将线程安全委托给这些状态变量。

如果一个状态变量是线程安全的,没有任何不变约束限制它的值,并且没有任何状态转换限制它的操作,那么它可以被安全发布。

4.4.向已有线程安全类添加功能

4.5 同步策略的文档化

当类的用户编写类线程安全性担保的文档,为类的维护者编写类的同步策略文档。

第五章 构建块
在实践中,委托是创建线程安全类最有效的策略之一:只需要用已有的线程安全类来管理所有状态即可。

5.1 同步容器

同步容器类包括两部分,一个是Vector和Hashtable,另一个是他们的同系容器。

5.1.1 同步容器中出现的问题

同步容器都是线程安全的。

5.1.2 迭代器和ConcurrentModificationException

当其他线程需要访问容器时,必须等待,知道迭代结束;如果容器很大,或者对每一个元素执行的任务耗时比较长,它们可能需要等待很长一段时间。

带迭代期间,对容器加锁的一个替代办法是复制容器。

5.1.3 隐藏迭代器

在一个可能发生迭代的容器中,各处都需要使用锁。

正如封装一个对象的状态,能够使他更容易地保持不变约束一样,封装它的同步则可以迫使她符合同步策略。

5.2 并发容器

并发容器替换同步容器,这种作法以有很小风险带来了可扩展性显著的提高。

5.2.1 ConcurrentHashMap

同步容器类在每个操作的执行期间都持有一个锁。

ConcurrentHashMapHashMap一样是一个哈希表,但是使用不同的锁策略。在ConcurrentHashMap以前,程序使用一个公共锁同步每一个方法,并严格地限制只能有一个线程可以同时访问容器。而ConcurrentHashMap使用一个更加细化的锁机制,叫做分离锁。允许更深层次的共享访问。

5.2.2 Map附件的原子操作

因为ConcurrentHashMap不能够在独占访问中被加锁,我们不能使用客户端加锁来创建新的原子操作。

5.2.3 CopyOnWriteArrayList

CopyOnWriteArrayList是同步List的一个并发替代品,提供更好的并发性,避免了在迭代器件对容器加锁和复制。(相似地,CopyOnWriteArraySet是同步set的一个并发替代品)

有界队列是强大的资源管理工具,用来建立可靠的应用程序: 他们抑制那些可以产生过多工作量,具有威胁的活动,从而让你的程序在面对超负荷工作时更加健壮。

5.5 Synchronizer

Synchronizer是一个对象,它根据本身的状态调节线程的控制流。

所有的Synchronizer都具有类似的结构特性:它们封装状态,而这些状态决定这线程执行到的某一点是通过不是被迫等待;它们还提供操控状态的方法,以及高效等待Synchronizer进入到期望状态的方法。

5.5.1 闭锁

闭锁(latch)是一种Synchronizer ,它可以延迟线程的进度直到线程达到终止(terminal)

5.5.2 FutureTask

FutureTask同样可以作为闭锁。FutureTask的计算是通过Callable实现的,它等价一个可携带结果的Runnable,并且有3个状态:等待,运行和完成。一旦FutureTask进入完成状态,它会永远停止在这个状态上。

5.5.3 信号量

计数信号量(Counting semaphore)用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数量。

5.5.4 关卡

关卡(barrier)类似于闭锁,他们都能够阻塞一组线程,知道某些事件发生。其中关卡与闭锁换件的不同在于,所有线程必须同时到达关卡点,才能继续处理。闭锁等待是事件;关卡等待的是其他线程。

第一部分总结

1.可变状态:所有并发问题都归结为如何协调访问并发状态。可变状态越少,保证线程安全就越容易。

2.尽量将域声明为final类型,除非他们的需要时可变的。

3.不可变对象天生是线程安全的。不可变对象极大地减轻了并发编程的压力。他们简单其安全,可以在没有锁或者防御性复制的情况下自由的共享。

4.封装使管理复杂度变得更可行。在对象中封装数据,让它们能更容易地保持不变;在对象中封装同步,使它能够更容易地遵守同步策略。

5.用锁来守护每一个可变变量;

6.对同一不变约束中的所有变量都使用相同的锁

7.在运行复合操作器件持有锁。

8.在非同步的多线程情况下,访问可变变量的程序是存在隐患的。

9.不要依赖于可以需要同步的小聪明。

10.在设计过程中就考虑线程安全。或者在文档中明确地说明它不是线程安全的。

11.文档化你的同步策略
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: