【问题标题】:Thread Safe Copying of Objects in JavaJava中对象的线程安全复制
【发布时间】:2012-04-17 12:42:11
【问题描述】:

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

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 和可变对象)并且我只更改静态对象的副本(以及正在线程中制作副本)。但是,我想在这里确认我的想法是正确的。

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

【问题讨论】:

  • 我会让不可变字段final 明确表示它们不会改变。这也可以提高线程安全性。

标签: java multithreading thread-safety synchronized


【解决方案1】:

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

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());
    }
}

这确保如果正在对实例进行修改,复制操作将等到修改完成。

【讨论】:

  • 另外,如果 OP(或将来修改他的代码的人)曾经修改过该静态数组......这是一个巨大的潜在线程安全问题。
  • 你不能像上面那样构建构造函数。 Java 不喜欢这样。
  • @Elliott,你是对的。我已经修改了构造函数,所以它是正确的。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

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

    这就是问题所在。

    如果它困扰您,您必须要么 synchronize 进行读/写操作,要么处理 Entry 实例发布。意思是,不允许其他线程访问正在构建的Entry 实例。

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】:

      • 当然,如果您有无限的计算资源。但那又何苦呢。 Actor 模式可能会使一项简单的任务复杂化,使其在 CPU/内存方面变得相当复杂和昂贵。
      【解决方案5】:

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

      【讨论】:

      • 再次阅读什么类型是什么......所以删除/添加。字符串实际上是相同的对象(不是副本)。整数不是。如果你只阅读而不改变另一个也没关系。哦,同步太大的区域是不好的(更容易死锁,太长的等待等),所以试着想想它可能在读取的同时被改变的地方。对于上面的答案,字符串不需要同步,因为它们是不可变的(如果你想要一个可变的字符串,请使用 stringbuffer 等)。 ints 可能没有最新状态(但如果没有在任何地方更改,则它是最新状态)
      猜你喜欢
      • 2013-06-23
      • 1970-01-01
      • 2011-04-07
      • 1970-01-01
      • 1970-01-01
      • 2013-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多