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
变量,第一个示例之所以有效,是因为