Java 从线程返回时设置非基元类型
我的问题是:为什么在返回线程时设置非原语类型有效 以下工作:Java 从线程返回时设置非基元类型,java,multithreading,Java,Multithreading,我的问题是:为什么在返回线程时设置非原语类型有效 以下工作: final int[] newTask = new int[1]; try{ Thread thread = new Thread(new Runnable(){ @Override public void run(){ newTask[0] = someMethod(); return;
final int[] newTask = new int[1];
try{
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
newTask[0] = someMethod();
return;
}
});
thread.start();
Thread.sleep(3000);
if(thread.isAlive()){
thread.interrupt();
newTask[0] = null;
}
}catch(InterruptedException ie){
log.error("Timeout", ie);
}
以下内容不适用:
final int newTask;
try{
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
newTask = someMethod();
return;
}
});
thread.start();
Thread.sleep(3000);
if(thread.isAlive()){
thread.interrupt();
newTask = null;
}
}catch(InterruptedException ie){
log.error("Timeout", ie);
}
非原语变量在从线程返回时起作用,但原语不起作用。为什么?在第一种情况下,
newTask
是对数组的引用。无法更改引用,因为它是final
。可以修改数组的内容,因为数组不是不变的
在第二种情况下,newTask
是一个基本值。它无法更改,因为它是最终版本
相同行为的一个简单示例如下:
final StringBuilder buf = new StringBuilder();
buf.append('x'); /* Modified the mutable object; no problem. */
buf = new StringBuilder(); /* Compiler error: you can't reassign final var */
它与线程或原语值与引用类型之间的差异无关。在第一种情况下,
newTask
是对数组的引用。无法更改引用,因为它是final
。可以修改数组的内容,因为数组不是不变的
在第二种情况下,newTask
是一个基本值。它无法更改,因为它是最终版本
相同行为的一个简单示例如下:
final StringBuilder buf = new StringBuilder();
buf.append('x'); /* Modified the mutable object; no problem. */
buf = new StringBuilder(); /* Compiler error: you can't reassign final var */
它与线程无关,也与原语值和引用类型之间的差异无关。您正在混淆一些东西。这不是关于原语或非原语。在第一个变体中,您通过两个线程访问对象,在第二个变体中,您试图为内部类的周围上下文分配一个变量 赋值无法工作,因为它将创建一个不确定状态,对于周围的代码,变量可能已初始化,也可能未初始化。请注意,Java语言在这里的限制性更大,因为它不关心您是否在多线程上下文中使用内部类 不建议通过修改共享对象从线程返回值。犯错误的方法太多了,尤其是对于根本不能保证线程安全的数组 如果您必须手动处理
线程
s,您仍然可以使用并发工具,这有助于避免线程错误:
Callable<Integer> task=new Callable<Integer>() {
public Integer call() throws Exception {
return someMethod();
}
};
FutureTask<Integer> f=new FutureTask<>(task);
new Thread(f).start();
try {
Integer i=f.get(3, TimeUnit.SECONDS);
// here we have a valid result
} catch (InterruptedException|ExecutionException ex) {
// log the failure
} catch (TimeoutException ex) {
f.cancel(true);
}
这在使用Java 8时变得更加干净:
ExecutorService threadPool=Executors.newCachedThreadPool();
Future<Integer> f=threadPool.submit(() -> someMethod());
try {
Integer i=f.get(3, TimeUnit.SECONDS);
// here we have a valid result
} catch (InterruptedException|ExecutionException ex) {
// log the failure
} catch (TimeoutException ex) {
f.cancel(true);
}
threadPool.shutdown();
ExecutorService threadPool=Executors.newCachedThreadPool();
Future f=threadPool.submit(()->someMethod());
试一试{
整数i=f.get(3,时间单位为秒);
//这里我们有一个有效的结果
}捕获(InterruptedException | ExecutionException ex){
//记录故障
}捕获(TimeoutException例外){
f、 取消(真);
}
threadPool.shutdown();
当然,
ExecutorService
的全部要点是,您可以使用它提交多个任务,因此您可能会在应用程序启动时创建一个ExecutorService
,并在应用程序生命周期结束时调用shutdown()
。您正在混淆一些事情。这不是关于原语或非原语。在第一个变体中,您通过两个线程访问对象,在第二个变体中,您试图为内部类的周围上下文分配一个变量
赋值无法工作,因为它将创建一个不确定状态,对于周围的代码,变量可能已初始化,也可能未初始化。请注意,Java语言在这里的限制性更大,因为它不关心您是否在多线程上下文中使用内部类
不建议通过修改共享对象从线程返回值。犯错误的方法太多了,尤其是对于根本不能保证线程安全的数组
如果您必须手动处理线程
s,您仍然可以使用并发工具,这有助于避免线程错误:
Callable<Integer> task=new Callable<Integer>() {
public Integer call() throws Exception {
return someMethod();
}
};
FutureTask<Integer> f=new FutureTask<>(task);
new Thread(f).start();
try {
Integer i=f.get(3, TimeUnit.SECONDS);
// here we have a valid result
} catch (InterruptedException|ExecutionException ex) {
// log the failure
} catch (TimeoutException ex) {
f.cancel(true);
}
这在使用Java 8时变得更加干净:
ExecutorService threadPool=Executors.newCachedThreadPool();
Future<Integer> f=threadPool.submit(() -> someMethod());
try {
Integer i=f.get(3, TimeUnit.SECONDS);
// here we have a valid result
} catch (InterruptedException|ExecutionException ex) {
// log the failure
} catch (TimeoutException ex) {
f.cancel(true);
}
threadPool.shutdown();
ExecutorService threadPool=Executors.newCachedThreadPool();
Future f=threadPool.submit(()->someMethod());
试一试{
整数i=f.get(3,时间单位为秒);
//这里我们有一个有效的结果
}捕获(InterruptedException | ExecutionException ex){
//记录故障
}捕获(TimeoutException例外){
f、 取消(真);
}
threadPool.shutdown();
当然,
ExecutorService
的全部要点是,您可以使用它提交多个任务,因此您可能会在应用程序启动时创建一个ExecutorService
,并在应用程序生命周期结束时调用shutdown()
。我看不到这里涉及任何原语。在第一个版本中,您将在匿名内部类中设置一个数组元素(您可以这样做)。在第二个版本中,您正在更改匿名内部类中final变量的值(不允许这样做)。您不能重新初始化final
变量,第一个示例有效,因为您使用的是数组,您正在初始化final
数组中的一个元素,而不是对象本身。@JonSkeet第二个版本将newTask作为最终变量。第一个变量可能会编译,但它会被破坏,因为第一个线程在newTask[0]
中看到的内容的有效性不能得到保证。编写线程安全代码不仅仅是让代码通过编译器。顺便说一句,您不会“从线程返回”。返回是指在堆栈上放置一个值,以便调用函数从堆栈中检索。根据定义,线程是一个(单独的)(调用)堆栈,因此不能“返回”。您只能为其他线程修改堆内存来读取它。我在这里没有看到任何原语。在第一个版本中,您将在匿名内部类中设置一个数组元素(您可以这样做)。在第二个版本中,您正在更改匿名内部类中final变量的值(不允许这样做)。您无法重新初始化final
变量,第一个示例之所以有效,是因为