Java并发12:面向对象与并发
在Java 语言里,面向对象思想能让并发编程更简单。
封装共享变量
封装:将属性和实现细节封装在对象内部,外界对象只能通过目标对象提供的公共方法来间接访问这些内部属性。
Tip:将共享变量作为对象属性封装在内部,对所有公共方法制定并发访问策略。
public class Counter {
private long value;
//将计数器封装为对象,提供了线程安全的方法
synchronized long get(){
return value;
}
synchronized long addOne(){
return ++value;
}
}
Tip:很多变量实际上是不变的,如身份证号等,将它声明为final。
识别共享变量间的约束条件
约束条件决定了并发访问策略。
一定要识别出所有共享变量之间的约束条件,如果约束条件识别不足,很可能导致制定的并发访问策略南辕北辙。
共享变量之间的约束条件,反映在代码里,基本上都会有 if 语句,一定要特别注意竞态条件。
public class SafeWM {
// 库存上限
private final AtomicLong upper =
new AtomicLong(0);
// 库存下限
private final AtomicLong lower =
new AtomicLong(0);
// 设置库存上限
void setUpper(long v){
// 检查参数合法性
if (v < lower.get()) {
throw new IllegalArgumentException();
}
upper.set(v);
}
// 设置库存下限
void setLower(long v){
// 检查参数合法性
if (v > upper.get()) {
throw new IllegalArgumentException();
}
lower.set(v);
}
// 省略其他业务代码
}
第11行和第19行践行了库存上下限关系,但由于竞态条件导致线程不安全。
制定并发访问策略
方案策略
- 避免共享:主要由线程本地存储&为每个任务分配独立线程实现。
- 不变模式:Java领域较少,其他领域如 Actor 模式、CSP 模式以及函数式编程的基础都是不变模式。
- 管程和其他同步工具:管程以及并发包中适用于不同场景的工具。
Tips
- 优先使用成熟工具类:Java并发包
- 迫不得已使用同步原语:如
synchronized
、Lock
、Semaphore
。 - 避免过早优化:安全第一,先保证程序安全,出现性能瓶颈再优化。
思考
上述代码如何使其线程安全?
- 对
setUpper()
&setLower
加锁 - 将
upper
和lower
封装成类,避免静态条件产生。
public class Boundary {
private final int upper;
private final int lower;
public Boundary(int upper, int lower) {
if (upper <= lower) {
throw new IllegalArgumentException("参数错误!");
}
this.upper = upper;
this.lower = lower;
}
}
public class SafeWM {
private Boundary boundary;
public void setBoundary(Boundary boundary){
this.boundary = boundary;
}
}
别人的总结
我的理解是set方法线程不安全的点在于竞态条件中获取到的值通过了判断,但是在set的时候可能不是最新的。方法二类封装的两个属性都是不可变的,在setBoundary()方法中去赋值就不会存在值被更新的情况,类似于ThreadLocal的独立副本。
——大饶Raysir