Je travaille sur un projet multithread dans lequel je dois générer plusieurs threads pour mesurer les performances de bout en bout de mon code client, car je fais des tests de charge et de performances. J'ai donc créé le code ci-dessous qui utilise ExecutorService
.
Voici le code avec ExecutorService
:
public class MultithreadingExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(20);
for (int i = 0; i < 100; i++) {
executor.submit(new NewTask());
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
}
}
class NewTask implements Runnable {
@Override
public void run() {
//Measure the end to end latency of my client code
}
}
Énoncé du problème:
Maintenant, je lisais un article sur Internet. J'ai découvert qu'il y a également ThreadPoolExecutor
. J'ai donc confondu lequel je devais utiliser.
Si je remplace mon code ci-dessus à partir de:
ExecutorService executor = Executors.newFixedThreadPool(20);
for (int i = 0; i < 100; i++) {
executor.submit(new NewTask());
}
à:
BlockingQueue<Runnable> threadPool = new LinkedBlockingQueue<Runnable>();
ThreadPoolExecutor tpExecutor = new ThreadPoolExecutor(20, 2000, 0L, TimeUnit.MILLISECONDS, threadPool);
tpExecutor.prestartAllCoreThreads();
for (int i = 0; i < 100; i++) {
tpExecutor.execute(new NewTask());
}
cela fera-t-il une différence? J'essaie de comprendre quelle est la différence entre mon code d'origine en utilisant ExecutorService
et le nouveau code collé en utilisant ThreadPoolExecutor
. Certains de mes coéquipiers ont dit que le deuxième (ThreadPoolExecutor) était la bonne façon de l'utiliser.
Quelqu'un peut-il clarifier cela pour moi?
Voici la source de Executors.newFixedThreadPool
:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Il utilise en interne la classe ThreadPoolExecutor
avec la configuration par défaut comme vous pouvez le voir ci-dessus. Il existe maintenant des scénarios où la configuration par défaut ne convient pas, par exemple au lieu de LinkedBlockingQueue
une file d'attente prioritaire doit être utilisée, etc. Dans de tels cas, l'appelant peut travailler directement sur le ThreadPoolExecutor
sous-jacent en l'instanciant et en transmettant la configuration souhaitée à elle.
alors cela fera une différence?
Cela rendra votre code plus compliqué pour peu d'avantages.
J'essaie de comprendre quelle est la différence entre mon code d'origine qui utilise ExecutorService et le nouveau code, que j'ai collé qui utilise ThreadPoolExectuor?
Presque rien. Executors
crée un ThreadPoolExecutor pour faire le vrai travail.
Certains de mes coéquipiers ont dit que le deuxième (ThreadPoolExecutor) était la bonne façon de l'utiliser?
Ce n'est pas parce que c'est plus compliqué que c'est la bonne chose à faire. Les concepteurs ont fourni les méthodes Executors.newXxxx pour vous simplifier la vie et parce qu'ils s'attendaient à ce que vous utilisiez ces méthodes. Je vous suggère de les utiliser également.
Executors # newFixedThreadPool (int nThreads)
ExecutorService executor = Executors.newFixedThreadPool(20);
est fondamentalement
return new ThreadPoolExecutor(20, 20,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
2.
BlockingQueue<Runnable> threadPool = new LinkedBlockingQueue<Runnable>();
ThreadPoolExecutor tpExecutor = new ThreadPoolExecutor(20, 2000, 0L,
TimeUnit.MILLISECONDS, threadPool);
Dans le deuxième cas, vous augmentez simplement le maxPoolSize à 2000, dont je doute que vous auriez besoin.
Je crois qu'un autre avantage est avec RejectionHandler. Corrigez-moi si je me trompe
Dans le premier exemple, vous avez créé seulement 20 threads avec la déclaration ci-dessous
ExecutorService executor = Executors.newFixedThreadPool(20);
Dans le deuxième exemple, vous avez défini la plage de limites de thread entre 20 to 2000
ThreadPoolExecutor tpExecutor = new ThreadPoolExecutor(20, 2000, 0L,
TimeUnit.MILLISECONDS,threadPool);
Plus de threads sont disponibles pour le traitement. Mais vous avez configuré la file d'attente des tâches comme file d'attente illimitée.
ThreadPoolExecutor serait plus efficace si vous avez personnalisé plusieurs ou tous les paramètres ci-dessous.
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
RejectedExecutionHandler
serait utile lorsque vous définissez max capacity for workQueue
et le nombre de tâches qui ont été soumises à Executor sont supérieurs à workQueue
capacité.
Jetez un œil à la section Tâches rejetées dans ThreadPoolExecutor pour plus de détails.
Après 2 jours de GC out of memory exception
, ThreadPoolExecutor
m'a sauvé la vie. :)
Comme l'a dit Balaji,
[..] un autre avantage est avec RejectionHandler.
Dans mon cas, j'ai eu beaucoup de RejectedExecutionException
et en spécifiant (comme suit) la politique de rejet a résolu tous mes problèmes.
private ThreadPoolExecutor executor = new ThreadPoolExecutor(1, cpus, 1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadPoolExecutor.DiscardPolicy());
Mais fais attention! Cela ne fonctionne que si vous n'avez pas besoin d'exécuter tous les threads que vous soumettez à l'exécuteur testamentaire.
Pour plus d'informations sur ThreadPoolExecutor
jetez un œil à réponse de Darren