3

我想知道我是否使用正确的方法在不同的线程中调用某些东西。我在 Android 上做,但认为这是通用的 Java 问题。

我有一些参数的方法。可以说它们是int。

class Main1 {
  public static void mainOnlyWork(int x, int y) {
     // not important here.
  }
}

class Test {
  Handler mHandler;

  // called from main thread, stored as reference.
  public Test() {
    mHandler = new Handler();
  }

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new Runnable() {
      public void run() {
        Main1.mainOnlyWork(x, y);
      }
    });
  }

现在我的问题是,在没有任何类成员的情况下使用最终整数创建匿名可运行对象是否安全?如果我省略了 x 和 y 参数的最终关键字,Eclipse 会抱怨。在我看来,这是为了在这种情况下只使用常数。如果我通过的不是常数,可以吗?Java是否通过传递给这个函数来“使它”保持不变?

但我想使用 JNI 从本机调用 Test.callInMainThread。在我看来,Java 无法判断这些数字是否为常量。我可以相信 Java 可以创造一些魔力吗?它会一直这样工作吗?

我想也许我必须创建代理类,例如:

private abstract RunnableXY implements Runnable {
  public RunnableXY(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public int x;
  public int y;

  public abstract void run();
}

调用方法将使用:

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new RunnableXY(x,y) {
      public void run() {
        Main1.mainOnlyWork(this.x, this.y);
      }
    });
  }

这样,我可以保护值免受垃圾收集,直到使用 runnable 并将其丢弃。我是否必须创建包装器,或者在方法参数中标记 final x 是否安全?当我尝试它时,final 关键字工作得很好。但是在线程中,我不认为它现在是否有效是它始终有效的原因。它总是与final一起工作吗?如果有,它是如何制作的?如果参数不是原始类型而是Object,会有区别吗?

更新:我已经了解 Java 中 final 的含义。这不是问题所在。问题点是用于创建 Runnable 的变量范围在哪里。他们是本地人,这意味着在函数结束后他们的值不能被引用。当 Runnable 对象被传递给其他线程并等待执行时,这些值存储在哪里?

4

3 回答 3

3

在线程内部,参数和局部变量(位于方法调用堆栈上)的副本被获取。这是因为方法调用完成后,线程仍然存在。

并且变量必须是final的,以禁止覆盖方法中的原始变量,这会导致线程中的版本不同。这简直是​​误导。因此,让同名的两个版本表示相同的事情是一个问题。

精心的语言设计。

(有些简化,没有命名对称反转的情况,同样适用。)

于 2013-07-23T14:23:50.970 回答
2

从基础开始:Java在您传递参数时进行复制。您的方法接收的 x 和 y 不是传入的原始变量,它们是原始值的副本。当您声明这样的方法时,您传入的值不必是常量,但该方法接收的副本是1

callInMainThread(final int x, final int y) { ..... }

第二:不,您不必制作包装纸。当您访问外部范围的本地变量时,Java 编译器会自动生成字段来存储它们,就像您手动创建的包装器一样。这对你来说是透明的。

不能省略final的一个原因是,Java 没有实现任何机制来在方法的局部变量和匿名类中生成的字段之间传输对变量值的更改。此外,匿名类的寿命可能比方法调用长。如果匿名类在方法返回后读取或写入变量会发生什么?如果变量不是最终变量,您将无法再读取或写入它的值。


1实际上final变量不是常数。您可以为它们分配不同的值,但只能分配一次。最终方法参数的值是在调用方法时分配的,因此它们在方法的持续时间内几乎是恒定的。

于 2013-07-23T14:16:48.270 回答
1

基元类型总是按值传递。即使您在方法内部修改它们,它们的原始值(在方法外部)也永远不会改变。所以是的,它是安全的,因为这些值是方法的本地值(无论final是否)。

关于final参数中的关键字,它实际上只是阻止您重新分配值,它没有其他用途。这仅仅是为了代码安全。在 Java 中参数总是按值传递(对象引用也是按值传递),所以无论如何,任何重新分配都将是方法的本地。方法完成后,您在方法中所做的所有重新分配都将消失。例如,两者之间没有区别

public void test(String a) {
    a = "Hello";
}

public void test(final String a) {
    a = "Hello";
}

除了编译器会在第二种情况下引发错误。在第一种情况下,test()方法完成a时将恢复为原始值(它不会是"Hello")。因此,有效地最终参数使参数“恒定”(注意对象:您仍然可以修改对象状态,但不能修改对象的引用)。


final匿名类中需要关键字,因为您正在引用另一个类范围中的变量(在您的情况下,您的匿名Runnable实例正在引用Main1实例变量)。因此,如果这是在另一个线程中运行并且它们不是最终的,您可以在方法的持续时间内覆盖原始引用。这将使每个线程引用具有相同变量名的不同对象,这是令人困惑的,因此在语言设计中被阻止。


您不必创建任何包装器或其他引用。参数在方法期间已经被引用,不会被垃圾回收。

于 2013-07-23T14:13:10.940 回答