}
// 设置库存下限
void setLower(long v){
lower.set(v);
}
// 省略其他业务代码
}
虽说上面的代码是没有问题的,但是忽视了一个约束条件,就是**库存下限要小于库存上限**,这个约束条件能够直接加到上面的 `set` 方法上吗?我们先直接加一下看看效果(如下面代码所示)。我们在 `setUpper()` 和 `setLower()` 中增加了参数校验,这乍看上去好像是对的,但其实存在并发问题,问题在于存在竞态条件。这里我顺便插一句,其实当你看到代码里出现 if 语句的时候,就应该立刻意识到可能存在竞态条件。
我们假设库存的下限和上限分别是 (2,10),线程 A 调用 setUpper(5) 将上限设置为 5,线程 B 调用 setLower(7) 将下限设置为 7,如果线程 A 和线程 B 完全同时执行,你会发现线程 A 能够通过参数校验,因为这个时候,下限还没有被线程 B 设置,还是 2,而 5>2;线程 B 也能够通过参数校验,因为这个时候,上限还没有被线程 A 设置,还是 10,而 7<10。当线程 A 和线程 B 都通过参数校验后,就把库存的下限和上限设置成 (7, 5) 了,显然此时的结果是不符合**库存下限要小于库存上限**这个约束条件的。
```java
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);
}
// 省略其他业务代码
}
在没有识别出库存下限要小于库存上限这个约束条件之前,我们制定的并发访问策略是利用原子类,但是这个策略,完全不能保证库存下限要小于库存上限这个约束条件。所以说,在设计阶段,我们一定要识别出所有共享变量之间的约束条件,如果约束条件识别不足,很可能导致制定的并发访问策略南辕北辙。
共享变量之间的约束条件,反映在代码里,基本上都会有 if 语句,所以,一定要特别注意竞态条件。
三、制定并发访问策略
制定并发访问策略,是一个非常复杂的事情。应该说整个专栏都是在尝试搞定它。不过从方案上来看,无外乎就是以下“三件事”。
- 避免共享:避免共享的技术主要是利于线程本地存储以及为每个任务分配独立的线程。
- 不变模式:这个在 Java 领域应用的很少,但在其他领域却有着广泛的应用,例如 Actor 模式、CSP 模式以及函数式编程的基础都是不变模式。
- 管程及其他同步工具:Java 领域万能的解决方案是管程,但是对于很多特定场景,使用 Java 并发包提供的读写锁、并发容器等同步工具会更好。
接下来在咱们专栏的第二模块我会仔细讲解 Java 并发工具类以及他们的应用场景,在第三模块我还会讲解并发编程的设计模式,这些都是和制定并发访问策略有关的。
最后
金三银四到了,送上一个小福利!
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](
)**
本文已被CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】收录,自学编程路线及系列技术文章等资源持续更新中…
😕/codechina.csdn.net/m0_60958482/java-p7)收录,自学编程路线及系列技术文章等资源持续更新中…**