我能否在不使用ExecutorService的情况下使用Callable线程?

28

我可以在不使用ExecutorService的情况下使用Callable线程吗?我们可以在没有ExecutorService的情况下使用Runnable实例和Thread子类,而且这段代码可以正常工作。但是这段代码可以保持一致性:

public class Application2 {

    public static class WordLengthCallable implements Callable {
        public static int count = 0;
        private final int numberOfThread = count++;

        public Integer call() throws InterruptedException {
            int sum = 0;
            for (int i = 0; i < 100000; i++) {
               sum += i;
            }
            System.out.println(numberOfThread);
            return numberOfThread;
       }
   }
   public static void main(String[] args) throws InterruptedException {
       WordLengthCallable wordLengthCallable1 = new WordLengthCallable();
       WordLengthCallable wordLengthCallable2 = new WordLengthCallable();
       WordLengthCallable wordLengthCallable3 = new WordLengthCallable();
       WordLengthCallable wordLengthCallable4 = new WordLengthCallable();
       wordLengthCallable1.call();
       wordLengthCallable2.call();
       wordLengthCallable3.call();
       wordLengthCallable4.call();
       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.exit(0);
  }
}

使用ExecutorService可以让代码仅使用少量线程,那我哪里出错了吗?


2
你为什么直接调用 call() 方法?这样做会创建一个后台线程吗?这就像直接调用 Runnable 对象的 run() 方法,而不是将其放入 Thread 中并在 Thread 上调用 start()。而且你为什么不想使用 ExecutorService? - Hovercraft Full Of Eels
你看到了什么行为,它与你的期望有何不同? - Patrick Collins
1
类Thread没有带有Collable的构造函数!!!我提出问题是为了满足我的兴趣... - user3233853
但是针对您的直接问题的直接回答,据我所知,不,您不能使用Callable来创建后台线程而不使用ExecutorService。 - Hovercraft Full Of Eels
@HovercraftFullOfEels 请看 Holger 的答案,他使用了 start() 方法来解决问题,这显然是 OP 所想要的;那难道不会创建一个后台任务吗? - killjoy
4个回答

54

虽然 interface 通常是为特定用例创建的,但它们并不限制在该方式中使用。

给定一个 Runnable,您可以将其提交到 ExecutorService,或将其传递给 Thread 的构造函数,也可以像调用任何 interface 方法一样直接调用其 run() 方法,而无需涉及多线程。还有更多用例,例如 AWT EventQueue.invokeLater(Runnable),因此永远不要期望列表完整。

给定一个 Callable,您有相同的选项,因此重要的是强调,与您的问题所暗示的不同,直接调用 call() 不涉及任何多线程。它只是像任何其他普通方法调用一样执行该方法。

由于没有 Thread(Callable) 构造函数,因此在没有 ExecutorService 的情况下,使用 CallableThread 需要稍微多写一些代码:

FutureTask<ResultType> futureTask = new FutureTask<>(callable);
Thread t=new Thread(futureTask);
t.start();
// …
ResultType result = futureTask.get(); // will wait for the async completion

10
简单明了地回答是,如果您想要使用Callable创建和运行后台线程,并且肯定要获取Future对象或一组Futures,则需要使用ExecutorService。如果没有Future,您将无法轻松地获取从Callable返回的结果或轻松地捕获生成的异常。当然,您可以尝试将Callable包装在Runnable中,然后在Thread中运行,但这会引出一个问题,即为什么这样做,因为这样做会失去很多东西。

编辑
你在评论中问道:

您的意思是像下面的代码一样,它可以工作吗?

public class Application2 {
    public static class WordLengthCallable implements Callable {
    public static int count = 0;
    private final int numberOfThread = count++;

    public Integer call() throws InterruptedException {
        int sum = 0;
        for (int i = 0; i < 100000; i++) {
            sum += i;
        }
        System.out.println(numberOfThread);
        return numberOfThread;
    }
}
    public static void main(String[] args) throws InterruptedException {
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    public static class MyRunnable implements Runnable {

        @Override
        public void run() {
            try {
                new WordLengthCallable().call();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我的回复:是的。链接中的代码“基本上”是有效的。它确实创建了后台线程,但在Callables中执行的计算结果被丢弃,所有异常都被忽略。这就是我所说的“因为这样你会失去很多”。
  ExecutorService execService = Executors.newFixedThreadPool(THREAD_COUNT);
  List<Future<Integer>> futures = new ArrayList<>();
  for (int i = 0; i < THREAD_COUNT; i++) {
     futures.add(execService.submit(new WordLengthCallable()));
  }
  for (Future<Integer> future : futures) {
     try {
        System.out.println("Future result: " + future.get());
     } catch (ExecutionException e) {
        e.printStackTrace();
     }
  }

  Thread.sleep(1000);
  System.out.println("done!");
  execService.shutdown();
编辑2
或者如果您希望按照发生的顺序返回结果,则使用CompletionService将ExecutorService包装起来,这是我以前从未尝试过的东西:
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CompletionServiceExample {
   public static class WordLengthCallable implements Callable<Integer> {
      private Random random = new Random();

      public Integer call() throws InterruptedException {
         int sleepTime = (2 + random.nextInt(16)) * 500;
         Thread.sleep(sleepTime);
         return sleepTime;
      }
   }

   private static final int THREAD_COUNT = 4;

   public static void main(String[] args) throws InterruptedException {
      ExecutorService execService = Executors.newFixedThreadPool(THREAD_COUNT);
      CompletionService<Integer> completionService = new ExecutorCompletionService<>(
            execService);

      for (int i = 0; i < THREAD_COUNT; i++) {
         completionService.submit(new WordLengthCallable());
      }
      execService.shutdown();

      try {
         while (!execService.isTerminated()) {
            int result = completionService.take().get().intValue();
            System.out.println("Result is: " + result);
         }
      } catch (ExecutionException e) {
         e.printStackTrace();
      }

      Thread.sleep(1000);
      System.out.println("done!");
   }
}

你的意思是这段代码 https://gist.github.com/AndrienkoAleksandr/b78b0e4e17b9c4facf90 是有效的吗? - user3233853
@user3233853:我更新了我的答案,包括使用CompletionService的代码,这是我以前从未使用过的东西,但它允许每个任务在完成并返回结果后立即继续执行,而不必等待其他尚未完成的任务。 - Hovercraft Full Of Eels
非常有趣,我们可以使用Callable和FutureTask:https://gist.github.com/AndrienkoAleksandr/747b5de2494f78e9be35 - user3233853
@user3233853:以这种方式使用FutureTask类似于使用Runnable包装器。我需要检查一下它是否能够充分捕获异常。 - Hovercraft Full Of Eels

5
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class MainClass {
    public static void main(String[] args) {
        try {
            Callable<String> c = () -> {
                System.out.println(Thread.currentThread().getName());
                return "true";
            };
            FutureTask<String> ft = new FutureTask<String>(c);
            Thread t = new Thread(ft);
            t.start();

            String result = ft.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/*
   Output: 
   Thread-0 
   true
 */

1
你可以直接从自己的线程中使用Callable的call()方法或Runnable的run()方法。然而,在特殊情况下这应该是最后的选择(例如集成遗留代码或单元测试)。扫描器可能会检测到这一点,并警告您可能存在的架构问题,因此最好不要这样做。
你还可以使用自己的ExecutorService(或使用Guava的MoreExecutors.sameThreadExecutor()),它基本上在调用线程中进行调用。这将使你对接口的“不干净”使用隔离到这个Executor,并允许它在任何时候使用不同的Executor。
顺便说一句:当你继承Thread时,你永远不应该在没有start/stop的情况下使用它,因为这可能会导致泄漏。这也是为什么bug扫描器警告直接调用run()方法的原因之一。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接