如何在Java的Runnable类中保持对象参数不变?

5

我有一个类似这样的Runnable类:

Class R1 implements Runnable {
  private static final Log LOGGER = LogFactory.getLog(R1.class);
  private final ObjectClass obj;
  private final SomeService service;

  public R1(ObjectClass obj, SomeService service) {
     this.obj = obj;
     this.service = service;
  }

  @override
  public void run() {
    String value = this.obj.getSomeValue();
    LOGGER.debug("Value is " + value);
    // some actions, such as:
    // service.someMethod(obj);
  }
}

我使用ExecutorService对象来执行R1并将R1放入队列中。但是,稍后在R1之外,我更改了传递给R1的ObjectClass中的值,因此getSomeValue()之后的动作不像我预期的那样运作。如果我想保持R1中ObjectClass对象的值不变,该怎么办?假设该对象很大并且有很多get和set方法。
为了使问题更清晰,我需要将obj传递到一个服务类对象中,该对象也用作runnable类的参数。我已相应地更改了原始代码。

"String value = this.obj.getSomeValue();" - Shahzeb
7个回答

1

根据评论,显然我的建议解决方案存在问题。

因此,请遵循其他关于创建新实例并复制所需属性的建议。或者创建一个轻量级数据对象,保存您需要的属性。无论哪种方式,我认为您需要两个实例来完成您想要的操作。


我建议您可以实现一个克隆方法来创建一个新的实例。

http://download.oracle.com/javase/1,5,0/docs/api/java/lang/Cloneable.html

这里的问题是您将实例传递到了R1类中,但它仍然是同一个单一实例,因此对它的更改将影响其他所有内容。因此,实现一个克隆方法将允许您轻松创建实例的副本,可以在R1类中使用,同时允许您对原始实例进行进一步更改。
在您的R1类中,
public R1(ObjectClass obj) {
   //this.obj = obj;
   this.obj = obj.clone();
}

顺便说一句,你必须亲自实现这个方法。它不会自动给你提供一个深度复制。


那不会有帮助 - 你仍然会遇到同样的问题。这是一个同步/发布问题。 - Bohemian
1
克隆通常不建议作为解决方案的一部分,而是问题的一部分。例如,请参见https://dev59.com/NHRB5IYBdhLWcg3wJUau - Ryan Stewart
@Bohemian 我不同意。他说R1在执行之前被放置在队列中,在实例化和执行之间,他修改了R1对象属性,这使得它在运行时得到了不同的结果。他似乎也坚持存储整个对象,这就是为什么我建议制作一个副本,这样他就有了两个实例,而不是一个,这样对第一个实例的更改不会影响第二个实例。除非这是executorService特定的一些特殊功能(我以前没有使用过),否则我为我的疏忽道歉。 - Daryl Teo
@RyanStewart 我不知道Cloneable存在问题。但是对于这种情况,即使他没有实现cloneable,我认为他的解决方案需要两个对象实例...无论他是通过复制还是通过另一个实例化和属性复制来实现的。无论如何,我还是点了赞 :) - Daryl Teo
我最终使用了一个自制的克隆类来执行深度克隆任务。 - newguy

1

根据您的程序性质,有几个选项可供选择。

您可以“谨慎地覆盖克隆”(Effective Java中的第11项)并在将其交给可运行对象之前克隆该对象。如果覆盖克隆对您不起作用,则最好执行以下操作之一:

  1. 手动创建对象的新实例并从obj复制值。
  2. 添加子集包含在obj中的数据。因此,您将不是将obj传递到构造函数中,而是传递someValue我倡导这种方法,以便您仅向R1提供其所需的数据,而不是整个对象。

或者,如果在执行R1之前obj中的数据发生变化并不重要,那么您只需要确保在执行R1obj不会发生变化。 在这种情况下,您可以在getSomeValue()方法中添加synchronize关键字,然后让R1obj上同步,如下所示:

@Override
public void run() {
  synchronize (obj) {
    String value = obj.getSomeValue();
  }
  // some actions.
}

我认为问题在于 obj 中的数据在 R1 执行之前发生了变化。所以我同意前半部分。 - Daryl Teo

0

假设:您不关心 R1 是否在旧数据上运行。

然后,您可以更改您的代码为:

public class R1 implements Runnable {
  private final String value;

  // Option 1: Pull out the String in the constructor.
  public R1(ObjectClass obj) {
    this.value = obj.getSomeValue(); // Now it is immutable
  }

  // Option 2: Pass the String directly into the constructor.
  public R1(String value) {
    this.value = value; // This constructor has no coupling
  }

  @Override public void run() {
    // Do stuff with value
  }
}

如果你想让R1操作最新的数据,而不是在构建它时的数据,那么你需要在R1和数据修改之间进行某种类型的同步。

0
将对象传递给构造函数,并不要保留对它的引用。

0
如果对象太大, 也许一个不可变的ParameterObject,带有足够的数据和方法,会更好。

0
如果可能的话,尝试使你的ObjectClass成为不可变类(不支持状态改变)。在Java中,你必须 "自己做";没有 'const' 对象的概念(就像C++中那样)。
也许你可以保留原始的ObjectClass,但创建一个新的ImmutableObjectClass类,在构造函数中将原始类传入。

0

这里的“问题”是,在Java内存模型下,线程可能(并且确实)会缓存字段值。这意味着如果一个线程更新了ObjectClass对象的字段,其他线程将无法“看到”更改-它们仍然在查看其缓存的(过时的)值。

为了使更改在线程之间可见,您有两个选择:

  1. 将您将在ObjectClass中更改的字段设置为volatile - volatile关键字强制线程不缓存字段的值(即始终使用最新值)
  2. 同步访问字段的读写操作-在同步块中进行的所有更改对于在相同锁对象上同步的其他线程是可见的(如果您同步方法,则使用this对象作为锁)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接