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
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.
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);
}
}
Style Java 8:
Executor e = Runnable::run;
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();
}
}
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.
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);
}
};
}