【问题标题】:Best practice for synchronizing an object between threads在线程之间同步对象的最佳实践
【发布时间】:2021-07-06 18:42:27
【问题描述】:

我基本上有一个对象,它存储具有附加属性(如速度)的项目的 3D 位置。该位置由一个单独的线程每 100 毫秒计算一次。另一个线程访问这些属性并修改其中的一些。

模拟线程

// ...
double z = location.getZ();
// calculate new position
location.setZ(z);
// ...

其他话题

// ...
while(location.getZ() > 10); // Some busy waiting for demonstration
location.setSpeed(10);
// ...

我的第一个想法是对每个属性简单地使用volatile,但据我所知,对 volatile 属性的每个操作都必须是原子的。由于允许两个线程在某些情况下更改某些属性(例如速度),因此这似乎不起作用。

我的下一个想法是使用 java 的 synchronized 关键字并同步每个 getter 和 setter 结束整个模拟周期。

// Location.java
public synchronized double getZ() { /* ... */ }

// Simulation loop
synchronized(location) {
    // calculations are done here
}

现在我想知道这是正确的方法还是有更好的方法。

可以在synchronized 块内调用getZ() 还是在内部这个结果需要单独的锁?

【问题讨论】:

  • 1.当 2 个不同的线程根据某些规则改变一个共享结构时的架构似乎有点奇怪和危险。您可能会同时获得数据竞争和竞争条件。我建议三思而后行,例如,也许可以使用一些队列重新设计此解决方案。 2. 但是如果你坚持... :) 我会在位置结构中添加一个 java.util.concurrent.locks.ReentrantReadWriteLock 并使用锁来进行一致的读写而不是同步块。获取锁,读取/修改必填字段,释放锁。
  • 为什么位置有“速度”属性?我同意 Anatoly G 的观点;同步只能解决底层问题,并不能解决你设计的逻辑问题。

标签: java concurrency synchronization


【解决方案1】:

您的同步方法看起来是正确的。 Volatile 确保与下一次读取相同变量存在发生之前的关系。但它没有建立原子性。

您可以使用 synchronize 关键字来确保发生前发生和原子性都正常工作。只需确保在 getter 和 setter 方法中锁定同一个监视器对象即可。

Getter 方法也需要同步。否则,getter 方法可以从本地 CPU 缓存中读取变量值,而不是从主内存中读取,因为之前发生的属性未建立。所以其他一些线程可能已经使用同步的 setter 更新了主内存中的值,但 getter 仍在从 CPU 缓存中读取过时的缓存值。

【讨论】:

  • 再好不过了!很好
  • 感谢您的回复!因此,如果我在模拟威胁的synchronized 块内调用Location 的getter 或setter(带有synchronized 关键字的方法),这不会导致死锁,因为我两次获得同一对象的锁?跨度>
  • Java 锁对于同一个线程是可重入的。这意味着如果一个线程已经获得了一个监视器对象,然后它再次尝试获取同一个监视器对象上的锁,它将无缝工作。我认为在您的情况下,您在“位置”对象上有一个同步块。并且您的吸气剂位于“this”对象上,该对象也是一个位置对象实例。如果两个对象都引用单个对象实例,那么您应该没有任何问题。它将按照可重入锁方案工作。
  • 在 X86 和 ARM 上,缓存总是一致的。所以你不能读取一个陈旧的值。
【解决方案2】:

另一种方法是在更新时替换整个对象。

例如

class State{
    final int x,y;
    ... constructor + getters
}

volatile State state;

public void update(int x, int y){
    this.state = new State(x,y);
}

public void useState(){
    State s = state;
    print(s.x+" "+s.y);
}

最大的好处是你总是可以返回一个一致的对象;不是具有介于两者之间的状态的对象。由于状态对象是不可变的,因此它使并发变得容易得多。

【讨论】:

  • 感谢您的建议!据我了解,当对象引用发生变化时,威胁会得到通知,对吗?这两种方法的性能有何不同?
  • 如果您写入 volatile,另一个线程将看到更改。我会更关心你系统的旋转。
  • 旋转是指轮询另一个线程 (while(location.getZ() > 10);) 对吧?
  • 正确。可能您想使用某种同步原语来防止 CPU 忙碌。
  • 当我使用 volatile 方法时,如何防止竞争条件或第二个线程的更改是否有任何影响?模拟线程有没有看到变化?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2019-11-20
  • 2012-04-12
  • 2015-05-02
相关资源
最近更新 更多