正在使用;“最后的”;Java中的参数线程安全
在本例中,将参数正在使用;“最后的”;Java中的参数线程安全,java,thread-safety,Java,Thread Safety,在本例中,将参数obj声明为final是否足以在下面的线程中安全使用它 public void doSomethingAsync (final Object obj) { Thread thread = new Thread () { @Override public void run () { ... do something with obj ... } } thread.start (); } 乍一看似乎还不错。调用者调用doSomethingAsync并将ob
obj
声明为final是否足以在下面的线程中安全使用它
public void doSomethingAsync (final Object obj)
{
Thread thread = new Thread ()
{
@Override public void run () { ... do something with obj ... }
}
thread.start ();
}
乍一看似乎还不错。调用者调用doSomethingAsync
并将obj
缓存到线程中需要时
但是,如果对doSomethingAsync
进行了大量调用,使得这些调用在线程对obj
执行任何操作之前完成,会发生什么情况呢
如果Java编译器只是将obj变成一个成员变量,那么对
doSomethingAsync
的最后一次调用将覆盖obj
的先前值,从而使线程的先前调用使用错误的值。或者,编译器是否为obj
生成一个队列或某个维度的存储,以便每个线程获得正确的值?根据原始海报和我在聊天中的对话,在此处进行大编辑
Peri真正的问题似乎是关于Java存储局部变量(如“obj”)以供线程使用的方式。如果你想自己用谷歌搜索它,这叫做“捕获变量”。有一个很好的讨论
基本上,当本地类被实例化时,所有本地变量(存储在堆栈上的变量)加上“this”指针都被复制到本地类(本例中为线程)中
为了便于评论,下面是原始答案。但现在它已经过时了
每次调用doSomethingAsync
都是在创建一个新线程。如果对特定对象只调用一次doSomethingAsync
,然后在调用线程中修改同一对象,则不知道异步线程将执行什么操作。在调用线程中修改对象之前、在调用线程中修改之后,甚至在调用线程中同时修改对象时,它可能会“对对象执行某些操作”。除非对象本身是线程安全的,否则这将导致问题
类似地,如果对同一对象调用两次doSomethignAsync
,则不知道哪个异步线程将首先修改该对象,也不能保证它们不会同时作用于同一对象
最后,如果使用两个不同的对象调用doSomethignAsync
两次,那么您不知道哪个异步线程将首先作用于自己的对象,但您不在乎,因为它们不会相互冲突,除非对象具有正在修改的静态可变变量(类变量)
如果您要求一个任务在另一个任务之前完成,并且按照提交的顺序完成,那么单线程ExecutorService就是您的答案。乍一看,这似乎很好。调用方调用doSomethingAsync,obj被缓存,直到线程中需要为止
对象未被“缓存”,变量引用仅不能分配给另一个对象。final
关键字只能防止变量被重新分配,但不能防止被引用的对象发生变异
但是,如果对doSomethingAsync进行了大量调用,使得它们在线程对obj执行任何操作之前完成,那么会发生什么呢
如果线程修改被引用的对象,那么行为将是未定义的,它们将竞争该对象,并且它们对该对象的引用可能具有“旧”值,因为该对象在线程之间不同步。如果对象是不可变的,它没有状态并且不能更改,那么它本质上是线程安全的
如果Java编译器只是将obj变成一个方法变量,那么对doSomethingAsync的最后一次调用将覆盖obj的先前值,从而使线程的先前调用使用错误的值。或者,编译器是否为obj生成一个队列或某个维度的存储,以便每个线程获得正确的值
编译器不保证线程按顺序执行,线程并行运行。这就是为什么synchronize
关键字存在的原因,这样您就可以保证当您引用对象时,您引用的对象的状态与所有其他线程看到的相同。显然,这是以性能为代价的,因此建议只将不可变对象传递到线程中,这样就不必在每次对对象执行操作时同步线程
如果Java编译器只是将obj变成一个成员变量,那么对doSomethingAsync的最后一次调用将覆盖obj的先前值,从而使线程的先前调用使用错误的值
不,这不会发生。对doSomethingAsync
的后续调用无法覆盖先前调用doSomethingAsync
捕获的obj
。即使您删除了最后一个关键字(假设java允许您在这一次这么做),这仍然有效
我认为您的问题最终是关于闭包是如何在java中实现的。但是,您的代码没有以正确的方式演示复杂性,因为代码甚至没有尝试在相同的词法范围内修改变量obj
在某种程度上,Java并没有真正捕获变量obj
,而是它的值。您可以用不同的方式编写代码,总体效果相同:
class YourThread extends Thread {
private Object param;
public YourThread (Object obj){
param = obj;
}
@Override
public void run(){
//do something with your param
}
}
您不再需要最后一个关键字:
public void doSomethingAsync (Object obj){
Thread t = new YourThread (obj);
t.start();
}
现在,假设您创建了两个线程实例,第二个实例如何修改作为参数传递给第一个实例的内容
其他语言的闭包 在其他语言中,神奇的事情确实会发生,但要表现出来,您需要编写稍微不同的代码:
public void doSomethingAsync (Object obj){
//Here let's assume obj is not null
Thread thread = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread.start ();
obj = null;
}
这不是有效的Java代码,但在某些语言中,这样的代码是允许的。当执行run
方法时,线程可能会将obj
视为n
public void doSomethingAsync (Object obj){
Thread thread1 = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread1.start ();
Thread thread2 = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread2.start ();
}