【问题标题】:Can I use Callable threads without ExecutorService?我可以在没有 ExecutorService 的情况下使用 Callable 线程吗?
【发布时间】:2014-10-03 13:34: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,代码可以使用很少的线程。我的错误在哪里?

【问题讨论】:

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

标签: java multithreading concurrency executorservice java.util.concurrent


【解决方案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!");
         }
      }
      

      【讨论】:

      • @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() 方法时发出警报的原因之一。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-31
        • 2019-01-31
        • 2016-08-11
        • 2021-08-10
        • 2021-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多