如何确保多个线程可以安全地访问类字段?

时间:2020-03-06 14:34:23  来源:igfitidea点击:

当多个线程通过getter方法访问类字段时,如何维护线程安全性?同步关键字是否足够?

这安全吗?

public class SomeClass {
    private int val;

    public synchronized int getVal() {
        return val;
    }

    private void setVal(int val) {
        this.val = val;
    }
}

还是二传手会带来更多的并发症?

解决方案

以我的理解,我们应该在getter和setter方法上都使用sync,这就足够了。

编辑:这是一些有关同步的更多信息的链接,而并非如此。

如果我们也在此处的setter上使用" synchronized",则此代码是线程安全的。但是,它可能不够精细。如果我们有20个getter和setter且它们都已同步,则可能是在创建同步瓶颈。

在此特定实例中,使用单个int变量,然后消除" synchronized"并标记int字段为" volatile"也将确保可见性(每个线程在调用getter时将看到" val"的最新值),但是可能足以满足需求。例如,期望

int old = someThing.getVal();
 if (old == 1) {
    someThing.setVal(2);
 }

仅当已经为1时不正确,才将val设置为2. 为此,我们需要一个外部锁或者一些原子比较并设置方法。

我强烈建议我们阅读Brian Goetz等人的《 Java Concurrency In Practice》,它涵盖了Java的并发结构。

存在同步以防止线程干扰和内存一致性错误。通过在getVal()上进行同步,该代码可确保SomeClass上的其他同步方法也不会同时执行。由于没有其他同步方法,因此无法提供太多价值。另请注意,对基元的读取和写入具有原子访问权限。这意味着通过仔细的编程,不需要同步对字段的访问。

阅读同步。

不太确定为什么将其降至-3. 我只是在总结Sun的Synchronization教程的内容(以及我的经验)。

Using simple atomic variable access is
  more efficient than accessing these
  variables through synchronized code,
  but requires more care by the
  programmer to avoid memory consistency
  errors. Whether the extra effort is
  worthwhile depends on the size and
  complexity of the application.

除了Cowan的评论之外,我们还可以执行以下操作进行比较和存储:

synchronized(someThing) {
    int old = someThing.getVal();
    if (old == 1) {
        someThing.setVal(2);
    }
}

之所以可行,是因为通过同步方法定义的锁与对象的锁隐式相同(请参见Java语言规范)。

对于简单的对象,这可能就足够了。在大多数情况下,应避免使用synced关键字,因为我们可能会遇到同步死锁。

例子:

public class SomeClass {

  private Object mutex = new Object();
  private int    val   = -1; // TODO: Adjust initialization to a reasonable start
                             //       value
  public int getVal() {
    synchronized ( mutex ) {
      return val;
    }
  }

  private void setVal( int val ) {
    synchronized ( mutex ) {
      this.val = val;
    }
  }
}

确保只有一个线程可以读写本地实例成员。

阅读" Java中的并发编程:设计原理和模式(Java(Addison-Wesley))"一书,也许是http://java.sun.com/docs/books/tutorial/essential/concurrency/index.html也是有帮助的...

如果类仅包含一个变量,那么另一种实现线程安全的方法是使用现有的AtomicInteger对象。

public class ThreadSafeSomeClass {

    private final AtomicInteger value = new AtomicInteger(0);

    public void setValue(int x){
         value.set(x);
    }

    public int getValue(){
         return value.get();
    }

}

但是,如果添加其他变量以使它们相互依赖(一个变量的状态取决于另一变量的状态),则AtomicInteger将不起作用。

回应阅读" Java并发实践"的建议。