web-dev-qa-db-fra.com

Existe-t-il un ExecutorService qui utilise le thread actuel?

Ce que je recherche, c'est une manière compatible de configurer ou non l'utilisation d'un pool de threads. Idéalement, le reste du code ne devrait pas être affecté du tout. Je pourrais utiliser un pool de threads avec 1 thread mais ce n'est pas tout à fait ce que je veux. Des idées?

ExecutorService es = threads == 0 ? new CurrentThreadExecutor() : Executors.newThreadPoolExecutor(threads);

// es.execute / es.submit / new ExecutorCompletionService(es) etc
82
Michael Rutherfurd

Voici une implémentation Executor (pas ExecutorService, très simple) très simple qui utilise uniquement le thread actuel. Voler cela dans "Java Concurrency in Practice" (lecture essentielle).

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

ExecutorService est une interface plus élaborée, mais pourrait être gérée avec la même approche.

60
overthink

Vous pouvez utiliser MoreExecutors.newDirectExecutorService() de Guava ou MoreExecutors.directExecutor() si vous n'avez pas besoin d'un ExecutorService.

Si l'inclusion de la goyave est trop lourde, vous pouvez implémenter quelque chose de presque aussi bon:

public final class SameThreadExecutorService extends ThreadPoolExecutor {
  private final CountDownLatch signal = new CountDownLatch(1);

  private SameThreadExecutorService() {
    super(1, 1, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(),
        new ThreadPoolExecutor.CallerRunsPolicy());
  }

  @Override public void shutdown() {
    super.shutdown();
    signal.countDown();
  }

  public static ExecutorService getInstance() {
    return SingletonHolder.instance;
  }

  private static class SingletonHolder {
    static ExecutorService instance = createInstance();    
  }

  private static ExecutorService createInstance() {
    final SameThreadExecutorService instance
        = new SameThreadExecutorService();

    // The executor has one worker thread. Give it a Runnable that waits
    // until the executor service is shut down.
    // All other submitted tasks will use the RejectedExecutionHandler
    // which runs tasks using the  caller's thread.
    instance.submit(new Runnable() {
        @Override public void run() {
          boolean interrupted = false;
          try {
            while (true) {
              try {
                instance.signal.await();
                break;
              } catch (InterruptedException e) {
                interrupted = true;
              }
            }
          } finally {
            if (interrupted) {
              Thread.currentThread().interrupt();
            }
          }
        }});
    return Executors.unconfigurableScheduledExecutorService(instance);
  }
}
76
NamshubWriter

Style Java 8:

Executor e = Runnable::run;

51
lpandzic

J'ai écrit un ExecutorService basé sur le AbstractExecutorService.

/**
 * Executes all submitted tasks directly in the same thread as the caller.
 */
public class SameThreadExecutorService extends AbstractExecutorService {

    //volatile because can be viewed by other threads
    private volatile boolean terminated;

    @Override
    public void shutdown() {
        terminated = true;
    }

    @Override
    public boolean isShutdown() {
        return terminated;
    }

    @Override
    public boolean isTerminated() {
        return terminated;
    }

    @Override
    public boolean awaitTermination(long theTimeout, TimeUnit theUnit) throws InterruptedException {
        shutdown(); // TODO ok to call shutdown? what if the client never called shutdown???
        return terminated;
    }

    @Override
    public List<Runnable> shutdownNow() {
        return Collections.emptyList();
    }

    @Override
    public void execute(Runnable theCommand) {
        theCommand.run();
    }
}
12
Eric Obermühlner

Vous pouvez utiliser le RejectedExecutionHandler pour exécuter la tâche dans le thread actuel.

public static final ThreadPoolExecutor CURRENT_THREAD_EXECUTOR = new ThreadPoolExecutor(0, 0, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(), new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        r.run();
    }
});

Vous n'en avez besoin que d'un seul.

5
Peter Lawrey

J'ai dû utiliser le même "CurrentThreadExecutorService" à des fins de test et, bien que toutes les solutions suggérées soient agréables (en particulier celle mentionnant à la manière de la goyave ), j'ai trouvé quelque chose de similaire à ce que Peter Lawrey a suggéré - ici .

Comme mentionné par Axelle Ziegler ici , malheureusement la solution de Peter ne fonctionnera pas réellement à cause de la vérification introduite dans ThreadPoolExecutor sur le paramètre constructeur maximumPoolSize (ie maximumPoolSize ne peut pas être <=0).

Afin de contourner cela, j'ai fait ce qui suit:

private static ExecutorService currentThreadExecutorService() {
    CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
    return new ThreadPoolExecutor(0, 1, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), callerRunsPolicy) {
        @Override
        public void execute(Runnable command) {
            callerRunsPolicy.rejectedExecution(command, this);
        }
    };
}
4
fabriziocucci