我想知道我是否使用正确的方法在不同的线程中调用某些东西。 我是在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);
}
});
}
现在我的问题是,使用final int来创建没有任何类成员的匿名runnable是否安全?如果我省略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。我是否必须创建包装器,或者在方法参数中标记最终x是否安全?当我尝试它时,final关键字工作得很好。但是在线程中,我不认为它现在是否有效是它始终有效的原因。它最终是否有效?它是如何制作的?如果参数不是基本类型,而是对象?
,会有区别吗?更新: 我已经理解了Java的最终意义。这不是一个问题。问题在于创建Runnable时使用的变量范围。它们是本地人,这意味着在功能结束后,它们的值无法被引用。当Runnable对象传递给其他线程并等待执行时,存储的值是什么?
答案 0 :(得分:3)
在内部,参数和局部变量的副本(驻留在方法调用堆栈中)在线程中。这是因为在方法调用完成后,线程仍然存在。
并且变量必须是最终的,以禁止覆盖方法中的原始变量,这将导致在线程中具有不同的版本。这简直是误导。所以这是让两个版本的同名意思相同的问题。
精心设计语言。
(有些简化,没有命名对称的逆转案例,同样适用。)
答案 1 :(得分:2)
从基础知识开始:传递参数时,Java会生成副本。您的方法接收的x和y不是传入的原始变量,它们是原始值的副本。声明这样的方法时,传入的值不必是常量,但方法接收的副本是 1 :
callInMainThread(final int x, final int y) { ..... }
第二:不,你不必制作包装纸。当您访问外部作用域本地的变量时,Java编译器会自动生成用于存储它们的字段,就像您手动创建的包装器一样。这对你来说是透明的。
您不能省略final
的一个原因是Java没有实现任何机制来将更改传递给方法的局部变量和匿名类中生成的字段之间的变量值。此外,匿名类可能比方法调用寿命更长。如果匿名类在方法返回后读取或写入变量会发生什么?如果变量不是最终变量,则无法再读取其值或写入变量。
1 实际上final
变量不是常量。您可以为它们分配不同的值,但只能分配一次。调用方法时会分配最终方法参数的值,因此它们在方法持续时间内几乎保持不变。
答案 2 :(得分: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
个实例变量)。因此,如果这是在另一个线程中运行并且它们不是最终的,则可以在方法的持续时间内覆盖原始引用。这将使每个线程引用具有相同变量名的不同对象,这会令人困惑,因此它在语言设计中被阻止。
您不必创建任何包装器或其他引用。参数已在方法的持续时间内引用,不会被垃圾回收。