web-dev-qa-db-fra.com

Puis-je utiliser des threads appelables sans ExecutorService?

Puis-je utiliser des threads appelables sans ExecutorService? Nous pouvons utiliser des instances de Runnable et des sous-classes de Thread sans ExecutorService et ce code fonctionne normalement. Mais ce code fonctionne de manière cohérente:

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);
  }
}

Avec ExecutorService, le code fonctionne avec quelques threads. Où sont mes erreurs?

13
user3233853

Bien que interfaces soient souvent créés avec un cas d'utilisation prévu, ils ne sont jamais restreints pour être utilisés de cette manière.

Si vous avez Runnable, vous pouvez le soumettre à un ExecutorService ou le transmettre au constructeur de Thread ou vous pouvez invoquer directement sa méthode run(), comme vous pouvez invoquer n'importe quelle méthode interface sans utiliser plusieurs threads. Et il y a plus de cas d'utilisation, par exemple AWT EventQueue.invokeLater(Runnable) donc ne vous attendez jamais à ce que la liste soit complète.

Étant donné que vous avez Callable, vous avez les mêmes options. Il est donc important de souligner que, contrairement à ce que suggère votre question, invoquer directement call() n’entraîne aucun multi-threading. Il exécute simplement la méthode comme n'importe quelle autre invocation de méthode ordinaire.

Puisqu'il n'y a pas de constructeur Thread(Callable) utilisant une Callable avec une Thread sans une ExecutorService nécessite légèrement plus de code:

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

La réponse directe simple est que vous devez utiliser ExecutorService si vous souhaitez utiliser Callable pour créer et exécuter un fil d’arrière-plan, et certainement si vous souhaitez obtenir un objet Future ou une collection de contrats à terme. Sans l'avenir, vous ne pourriez pas obtenir facilement le résultat renvoyé par vos exceptions Callable ou catch catch générées. Bien sûr, vous pouvez essayer d’envelopper votre Callable dans un Runnable, puis de l’exécuter dans un Thread, mais cela soulèverait la question de savoir pourquoi, car vous perdriez beaucoup.


Modifier
Vous demandez en commentaire,

Voulez-vous dire comme le code ci-dessous, qui fonctionne?

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();
            }
        }
    }
}

Ma réponse: oui Le code dans le lien "sorte de" fonctionne. Oui, cela crée des threads en arrière-plan, mais les résultats des calculs effectués dans les Callables sont ignorés et toutes les exceptions sont ignorées. C’est ce que j’entends par "car, ce faisant, vous perdriez beaucoup".


par exemple.,

  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();

Modifier 2
Ou si vous souhaitez que les résultats soient renvoyés au fur et à mesure qu'ils apparaissent, utilisez un CompletionService pour envelopper votre ExecutorService, ce que je n'ai jamais tenté auparavant:

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!");
   }
}
5

Oui, vous pouvez utiliser directement la méthode call() d'un Callable ou la méthode run() d'un Runnable. Toutefois, cela devrait être votre dernier recours dans des circonstances spéciales (par exemple, l'intégration de codes hérités ou de tests unitaires). Les scanners peuvent détecter cela et vous alerter d'un éventuel problème d'architecture. Il est donc préférable de ne pas le faire.

Vous pouvez également utiliser votre propre variable ExecutorService (ou utiliser la fonction MoreExecutors.sameThreadExecutor() de Guava) pour effectuer l'appel dans le thread appelant. Ceci isolera votre utilisation "sale" de l'interface avec cet exécuteur et lui permettra d'utiliser un exécuteur différent quand vous le souhaitez.

BTW: soyez prudent, lorsque vous héritez de Thread, vous ne devriez jamais l'utiliser sans démarrer/arrêter car cela pourrait provoquer une fuite. C'est l'une des raisons pour lesquelles les analyseurs de bogues alertent directement sur l'appel des méthodes run ().

0
eckes
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
 */
0
Prashant K S