利用对象限制和委托构建线程安全的类--Java Concurrency In Practice C04读书笔记
[本文是我对Java Concurrency In Practice第三章的归纳和总结. ?转载请注明作者和出处, ?如有谬误, 欢迎在评论中指正. ]?
设计线程安全的类需要考虑:
1. 确定组成对象状态的变量.
2. 确定约束对象状态的不变式.
3. 建立并发访问对象状态的规则.
?
后置条件: 由于某些变量的取值是有限制范围的, 改变状态变量之后需要检查改变后的状态是否合法. 后置条件要求进行额外的同步. 比如一个计数器, 当前的值为17, 那么进行一次操作之后其取值只能是18, 其他值都是非法的.
前置条件: 需要满足一定的条件操作才能继续进行. 多线程环境下, 可以使用内置的wait和notify机制, 在条件满足时通知另一个线程继续往下执行. 也可以使用阻塞队列, 信号量等实现.?
?
对象限制
借助封装, 可以将对象限制在类范围内(作为类的私有成员), 方法内部(作为方法的局部变量), 或者线程范围内(只能在特定线程中访问该对象).
将对象限制在类范围内的例子:
DelegatingVehicleTracker只有一个状态变量: locations. locations是一个ConcurrentMap对象, 也就是说locations是线程安全的. 而且locations对象被限制在DelegatingVehicleTracker类范围内. 因此可以说, DelegatingVehicleTracker是一个线程安全的类因为locations是线程安全的. 换一种说法, DelegatingVehicleTracker将线程安全的责任委托给了locations对象.?
在构造函数里, 利用points的concurrent视图初始化locations. concurrent视图不同于synchronized视图, synchronized视图只是包装了一层同步, 方法的调用还有由原有的Map对象负责的, 因此JDK文档特别要求不能在外部继续调用原有Map的方法, 所以线程安全来源于约定, 如果开发者没有遵循约定, 线程安全就被破坏了. 但是concurrent视图并非如此, concurrent视图是对原有Map的浅拷贝, 也就是会遍历原有Map, 取出其中的每一个键值对, 然后将取出的键值对存入concurrent视图中. 因此concurrent视图仅和原有的Map共享了键和值的引用, 而由于Map的value(Point对象)是不可变的, 在外部直接调用原有的Map的任何方法, 都不会对concurrent视图造成影响.
我们可以总结出这样的规则: 如果类只包含一个状态变量, 那么类是否是线程安全的, 取决于该状态变量是否是线程安全的.
如果类中存在多个状态变量时, 委托规则是否仍然成立呢? 如下:
NumberRange类的状态变量lower和upper都是线程安全的对象, 但是lower和upper不是相互独立的, 两者需要满足不变式约束: lower <= upper. 假设lower和upper分别是0和10, 此时2个不同的线程同时调用setLower(5)和setUpper(4), 在某些不幸的时间点这2个方法的前置条件检查都能通过, 这就造成不变式约束不再成立. 因此NumberRange类不是线程安全的.
总结: 如果类中的状态变量之间是相互独立的, 那么线程安全的责任就可以委托给状态变量: 类是否线程安全, 取决于所有状态变量是否线程安全.
?