我可以在没有 ExecutorService 的情况下使用 Callable 线程吗?

Posted

技术标签:

【中文标题】我可以在没有 ExecutorService 的情况下使用 Callable 线程吗?【英文标题】:Can I use Callable threads without ExecutorService? 【发布时间】:2014-10-03 13:34:28 【问题描述】:

我们可以在没有 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,代码可以使用很少的线程。我的错误在哪里?

【问题讨论】:

为什么要直接调用call()方法呢?那如何创建一个后台线程?这就像直接调用 Runnable 对象的 run() 方法,而不是将其放入 Thread 并在 Thread 上调用 start()。为什么你不想使用 ExecutorService? 您看到了什么行为,与您的预期有何不同? Class Thread 没有 Collable 的构造函数!!!为了我的利益,我提出了问题...... 但要直接回答您的直接问题,据我所知,不,您不能在不使用 ExecutorService 的情况下使用 Callable 创建后台线程。 @HovercraftFullOfEels 见 Holger 的回答,他有使用 start() 的解决方案,这显然是我认为的 OP 的意思;这不会创建一个 bg 任务吗? 【参考方案1】:
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
 */

【讨论】:

【参考方案2】:

虽然interfaces 通常是根据预期的用例创建的,但它们绝不限于以这种方式使用。

给定一个Runnable,你可以将它提交给ExecutorService,或者将它传递给Thread的构造函数,或者你可以直接调用它的run()方法,就像你可以调用任何interface方法一样涉及线程。还有更多用例,例如AWT EventQueue.invokeLater(Runnable) 所以不要期望列表是完整的。

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

由于没有构造函数 Thread(Callable) 使用 CallableThread 而没有 ExecutorService 需要更多代码:

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

【讨论】:

【参考方案3】:

简单直接的答案是,如果您想使用 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!");
   

【讨论】:

你的意思是gist.github.com/AndrienkoAleksandr/b78b0e4e17b9c4facf90这个代码有效 @user3233853:我已经更新了我的答案以包含使用 CompletionService 的代码,这是我以前从未使用过的东西,但它允许每个任务完成并返回而无需等待尚未竞争任务。 很有意思,我们可以用FutureTask with Callable:gist.github.com/AndrienkoAleksandr/747b5de2494f78e9be35 @user3233853:以这种方式使用 FutureTask 类似于使用 Runnable 包装器。我将不得不检查它是否会充分捕获异常。【参考方案4】:

是的,您可以直接从您自己的线程中使用 Callable 的 call() 方法或 Runnable 的 run() 方法。然而,这应该是您在特殊情况下的最后手段(例如集成遗留代码或单元测试)。扫描程序可能会检测到这一点并提醒您可能存在架构问题,因此最好不要这样做。

您也可以使用自己的ExecutorService(或使用Guava 的MoreExecutors.sameThreadExecutor()),它基本上在调用线程中进行调用。这将隔离您对该 Executor 接口的“不干净”使用,并允许它在您需要时使用不同的 Executor。

顺便说一句:小心,当你从Thread 继承时,你不应该在没有启动/停止的情况下使用它,因为这可能会导致泄漏。这是错误扫描程序在直接调用 run() 方法时发出警报的原因之一。

【讨论】:

以上是关于我可以在没有 ExecutorService 的情况下使用 Callable 线程吗?的主要内容,如果未能解决你的问题,请参考以下文章

ExecutorService 的未来任务没有真正取消

在 ExecutorService 的提交和 ExecutorService 的执行之间进行选择

ExecutorService对象的shutdown 和shutdownNow 的区别

ExecutorService中submit和execute区别

ExecutorService 在超时后中断任务

java代码执行在没有断点和正常运行的调试中产生不同的结果。 ExecutorService 坏了吗?