Java 中对象的线程安全复制



我有一个类似于以下内容的静态类数组:

public class Entry {
    private String sharedvariable1= "";
    private String sharedvariable2= "";
    private int sharedvariable3= -1;
    private int mutablevariable1 = -1
    private int mutablevariable2 = -2;
    public Entry (String sharedvariable1, 
                  String sharedvariable2, 
                  int sharedvariable3) {
        this.sharedvariable1 = sharedvariable1;
        this.sharedvariable2 = sharedvariable2;
        this.sharedvariable3 = sharedvariable 3;
    }
    public Entry (Entry entry) {  //copy constructor. 
        this (entry.getSharedvariable1, 
              entry.getSharedvariable2, 
              entry.getSharedvaraible3);
    }
....
/* other methods including getters and setters*/
}

在我的程序中的某个时刻,我访问了这个对象的实例,并使用上面的复制构造函数制作它的副本。 然后,我更改上面两个可变变量的值。 该程序在多线程环境中运行。 请注意。 所有变量在线程化之前都使用其初始值进行设置。 只有在程序线程化并复制后,变量才会更改。 我相信它是线程安全的,因为我只读取静态对象,而不是写入它(甚至是共享变量 3,尽管只读取 int 和可变对象),并且我只对静态对象的副本进行更改(并且副本是在线程内进行的)。 但是,我想确认我的想法是正确的。

有人可以评估我在做什么吗?

它不是线程安全的。 您需要包装任何修改共享变量的内容:

synchronized (this) {
    this.sharedvariable1 = newValue;
}

对于二传手,您可以改为这样做:

public synchronized void setSharedvariable1(String sharedvariable1) {
    this.sharedvariable1 = sharedvariable1;
}

然后在复制构造函数中,您将执行类似的操作:

public Entry (Entry entry) {
    this();
    synchronized(entry) {
        this.setSharedvariable1(entry.getSharedvariable1());
        this.setSharedvariable2(entry.getSharedvariable2());
        this.setSharedvariable3(entry.getSharedvariable3());
    }
}

这可确保在对实例进行修改时,复制操作将等待修改完成。

它不是线程安全的,您应该在复制构造函数中同步。您正在从复制构造函数中的原始对象读取三个变量中的每一个。这些操作不是原子的。因此,当您读取第一个值时,第三个值可能会被另一个线程更改。在这种情况下,您有一个处于不一致状态的"复制"对象。

它不是线程安全的。我的意思是,这并不能保证使用同一Entry实例的多个线程的线程安全。

我在这里看到的问题如下:

  1. Thread 1开始构造一个Entry实例。它不会使该实例对其他线程访问隐藏。
  2. Thread 2使用其复制构造函数访问该实例,而该实例仍处于构造过程中。

考虑到Entry的字段private int sharedvariable3= -1;的初始值,结果可能是Thread 2创建的新"复制"实例的sharedvariable3字段将设置为0(java中int类字段的默认值)。

这就是问题所在。

如果它困扰您,您必须synchronize读/写操作,或者处理Entry实例发布。这意味着,不允许其他线程访问正在构造的Entry实例。

我真的不明白,为什么你认为私有实例变量是共享的。通常共享字段是静态的,不是私有的 - 我建议您不要共享私有实例变量。为了线程安全,您应该同步改变变量值的操作。

您可以使用同步关键字,但选择正确的监视器对象(我认为条目本身应该这样做)。另一种选择是使用java.util.concurrent的一些锁实现。通常,锁提供更高的吞吐量和更好的粒度(例如,多个并行读取,但在任何给定时间只有一个写入)。

您必须考虑的另一件事是所谓的记忆屏障。看看这篇有趣的文章 http://java.dzone.com/articles/java-memory-model-programer%E2%80%99s

您可以使用 volatile 关键字强制实施语义之前发生的事件。显式同步(锁或同步代码)也会跨越内存屏障,并在语义之前强制发生。

最后是一条一般性建议:您应该不惜一切代价避免共享可变状态。同步是一件很痛苦的事情(性能和维护方面)。由不正确的同步导致的错误非常难以检测。最好设计为不变性或孤立可变性(例如参与者)。

答案是它在概述的条件下是线程安全的,因为我只从静态状态下的变量中读取并且只更改副本。

最新更新