(原创)Callable、FutureTask中阻塞超时返回的坑点

直接上代码

import java.util.concurrent.Callable;
 
public class MyCallable implements Callable<String> {
 
    private long waitTime;
     
    public MyCallable(int timeInMillis){
        this.waitTime=timeInMillis;
    }
    @Override
    public String call() throws Exception {
        Thread.sleep(waitTime);
        return Thread.currentThread().getName();
    }
 
}

结果阻塞的代码

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
 
public class FutureTaskExample {
 
    public static void main(String[] args) {
        MyCallable callable1 = new MyCallable(1000);
        MyCallable callable2 = new MyCallable(2000);
 
        FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
        FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
 
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(futureTask1);
        executor.execute(futureTask2);
         
        while (true) 
        {
            try {
                if(futureTask1.isDone() && futureTask2.isDone()){
                    System.out.println("Done");
                    //shut down executor service
                    executor.shutdown();
                    return;
                }
                 
                if(!futureTask1.isDone()){
                //阻塞futureTask1
                System.out.println("FutureTask1 output="+futureTask1.get());
                }
                
                if(!futureTask2.isDone()){
                //阻塞futureTask2
                System.out.println("FutureTask2 output="+futureTask2.get());
                }

            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }catch(Exception e){
                //do nothing
            }
        }
         
    }
 
}

运行结果很简单,必须是:

FutureTask1 output=pool-1-thread-1
FutureTask2 output=pool-1-thread-2
Done

如果改为阻塞超时,先猜猜输出结果是什么。注意第37行代码有超时处理。

 1 import java.util.concurrent.ExecutionException;
 2 import java.util.concurrent.ExecutorService;
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.FutureTask;
 5 import java.util.concurrent.TimeUnit;
 6 import java.util.concurrent.TimeoutException;
 7  
 8 public class FutureTaskExample {
 9  
10     public static void main(String[] args) {
11         MyCallable callable1 = new MyCallable(1000);
12         MyCallable callable2 = new MyCallable(2000);
13  
14         FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
15         FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
16  
17         ExecutorService executor = Executors.newFixedThreadPool(2);
18         executor.execute(futureTask1);
19         executor.execute(futureTask2);
20          
21         while (true) 
22         {
23             try {
24                 if(futureTask1.isDone() && futureTask2.isDone()){
25                     System.out.println("Done");
26                     //shut down executor service
27                     executor.shutdown();
28                     return;
29                 }
30                  
31                 if(!futureTask1.isDone()){
32                 //阻塞futureTask1
33                 System.out.println("FutureTask1 output="+futureTask1.get());
34                 }
35                  
36                 System.out.println("Waiting for FutureTask2 to complete");
37                 String s = futureTask2.get(500L, TimeUnit.MILLISECONDS); //阻塞500毫秒
38                 if(s !=null){
39                     System.out.println("FutureTask2 output="+s);
40                 }
41                 else{
42                     System.out.println("FutureTask2 output is null");
43                 }
44             } catch (InterruptedException | ExecutionException e) {
45                 e.printStackTrace();
46             }catch(Exception e){
47                 //do nothing
48             }
49         }
50          
51     }
52  
53 }

如果说是这样的结果,那就错了

FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
FutureTask2 output is null
Waiting for FutureTask2 to complete
FutureTask2 output is null
FutureTask2 output=pool-1-thread-2
Done

最终输出

FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=pool-1-thread-2
Done

说明了一件事,即在超时期限内,如果未能获取线程返回值,futureTask2.get(500L, TimeUnit.MILLISECONDS) 将不对继续执行后面的代码,而是进行下一次的while操作了(并不是返回null),while的下一次循环,直到获取到了返回结果,String s才得以赋值,代码继续进行。

所以要慎用get(long timeout, TimeUnit unit)。

传统的理解是错误的

get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

大神 海子 曾对这个问题有质疑,认为会抛出异常,并赋空值,见:

http://www.cnblogs.com/dolphin0520/p/3949310.html#3318489

我尝试修改代码

String s="aa";
        while (true) 
        {
            try {
                if(futureTask1.isDone() && futureTask2.isDone()){
                    System.out.println("Done");
                    //shut down executor service
                    executor.shutdown();
                    return;
                }
                 
                if(!futureTask1.isDone()){
                //阻塞futureTask1
                System.out.println("FutureTask1 output="+futureTask1.get());
                }
                 
                System.out.println("Waiting for FutureTask2 to complete");
                s = futureTask2.get(500L, TimeUnit.MILLISECONDS); //阻塞500毫秒
                if(s !=null){
                    System.out.println("FutureTask2 output="+s);
                }
                else{
                    System.out.println("FutureTask2 output is null");
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }catch(Exception e){
                System.out.println("s is:"+s);
                //do nothing
            }
        }
View Code

s的预设值那里有改变:String s="aa";也没发现变为null,是没发生赋值。在异常中s也没有被赋空值。

所以在使用get(long timeout, TimeUnit unit)的时候,变量初始最好能给一个空值,这样就不会产生奇怪的结果,这也是合理的编程习惯。

原文地址:https://www.cnblogs.com/starcrm/p/5010863.html