Nommer les threads et les pools de threads d'ExecutorService


228

Disons que j'ai une application qui utilise le Executorcadre en tant que tel

Executors.newSingleThreadExecutor().submit(new Runnable(){
    @Override
    public void run(){
        // do stuff
    }
}

Quand je lance cette application dans le débogueur, un thread est créé avec le nom suivant (par défaut): Thread[pool-1-thread-1]. Comme vous pouvez le voir, ce n'est pas très utile et pour autant que je sache, le Executorcadre ne fournit pas un moyen facile de nommer les threads ou les pools de threads créés.

Alors, comment peut-on fournir des noms pour les threads / thread-pools? Par exemple, Thread[FooPool-FooThread].

Réponses:


118

Vous pourriez fournir un ThreadFactoryà newSingleThreadScheduledExecutor(ThreadFactory threadFactory). L'usine sera responsable de la création des threads et pourra les nommer.

Pour citer le Javadoc :

Créer de nouveaux threads

De nouveaux threads sont créés à l'aide d'un ThreadFactory. Sauf indication contraire, a Executors.defaultThreadFactory()est utilisé, ce qui crée des threads pour qu'ils soient tous identiques ThreadGroupet avec le même NORM_PRIORITYstatut de priorité et de non-démon. En fournissant un autre ThreadFactory, vous pouvez modifier le nom du fil, groupe de threads, la priorité, le statut démon, etc. Si un ThreadFactoryne parvient pas à créer un fil quand on lui demande en retournant nul de newThread, l'exécuteur testamentaire continuera, mais peut - être pas en mesure d'exécuter toutes les tâches


283

La goyave a presque toujours ce dont vous avez besoin .

ThreadFactory namedThreadFactory = 
  new ThreadFactoryBuilder().setNameFormat("my-sad-thread-%d").build()

et le passer à votre ExecutorService.


3
C'est fantastique!
Martin Vseticka

25
C'est triste! :-(
exic

Je ne sais pas où trouver "goyave". Il y a beaucoup de parties dans la goyave de Google et il y a des dizaines de bibliothèques du même nom. Je suppose que vous voulez dire search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . Est-ce correct? Le lien que vous fournissez suggère qu'il provient de Google, mais Google possède également une demi-douzaine d'artefacts sur Maven / Sonatype nommés "goyave".
Jason

@Jason - Si vous écrivez un projet Java non trivial, vous devriez probablement déjà avoir la goyave comme dépendance. Et le voici: github.com/google/guava
pathikrit

@pathikrit, merci! Je pense que j'ai besoin d'étudier la goyave plus :-)
Jason

95

Vous pouvez essayer de fournir votre propre fabrique de threads, qui créera un thread avec les noms appropriés. Voici un exemple:

class YourThreadFactory implements ThreadFactory {
   public Thread newThread(Runnable r) {
     return new Thread(r, "Your name");
   }
 }

Executors.newSingleThreadExecutor(new YourThreadFactory()).submit(someRunnable);

58

Vous pouvez également modifier le nom de votre thread par la suite, pendant l'exécution du thread:

Thread.currentThread().setName("FooName");

Cela pourrait être intéressant si, par exemple, vous utilisez la même ThreadFactory pour différents types de tâches.


7
Cela a bien fonctionné car, comme FlorianT l'a décrit, j'ai de nombreux types de threads différents et je ne voulais pas avoir à créer plusieurs objets ThreadFactory juste pour le nom. J'ai appelé Thread.currentThread (). SetName ("FooName"); comme première ligne de chaque méthode run ().
Robin Zimmermann

5
Un problème mineur avec c'est lorsque le comportement d'échec décrit dans la documentation se produit: (Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.). Si ExecutorService remplace le thread, il sera nommé par ThreadFactory. Là encore, voir le nom disparaître pendant le débogage pourrait être un indicateur utile.
sethro

Tout simplement superbe! Je vous remercie.
asgs

1
Comme le dit l'autre réponse, c'est une méthode rapide et sale pour définir le nom, et si vous le faites avec plusieurs threads, tous auront le même nom !!
Tano

Pourrait vouloir redéfinir le nom du thread sur original à la sortie, car il peut conserver le nom même s'il travaille sur différentes tâches non liées.
Dustin K

51

le BasicThreadFactory apache commons-lang est également utile pour fournir le comportement de dénomination. Au lieu d'écrire une classe interne anonyme, vous pouvez utiliser le générateur pour nommer les threads comme vous le souhaitez. Voici l'exemple des javadocs:

 // Create a factory that produces daemon threads with a naming pattern and
 // a priority
 BasicThreadFactory factory = new BasicThreadFactory.Builder()
     .namingPattern("workerthread-%d")
     .daemon(true)
     .priority(Thread.MAX_PRIORITY)
     .build();
 // Create an executor service for single-threaded execution
 ExecutorService exec = Executors.newSingleThreadExecutor(factory);

30

Si vous utilisez Spring, CustomizableThreadFactoryvous pouvez définir un préfixe de nom de thread.

Exemple:

ExecutorService alphaExecutor =
    Executors.newFixedThreadPool(10, new CustomizableThreadFactory("alpha-"));

Alternativement, vous pouvez créer votre en ExecutorServicetant que bean Spring en utilisant ThreadPoolExecutorFactoryBean- alors les threads seront tous nommés avec le beanName-préfixe.

@Bean
public ThreadPoolExecutorFactoryBean myExecutor() {
    ThreadPoolExecutorFactoryBean executorFactoryBean = new ThreadPoolExecutorFactoryBean();
    // configuration of your choice
    return executorFactoryBean;
}

Dans l'exemple ci-dessus, les threads seront nommés avec myExecutor-préfixe. Vous pouvez définir le préfixe explicitement sur une valeur différente (par exemple. "myPool-") En définissant executorFactoryBean.setThreadNamePrefix("myPool-")le bean d'usine.


vous ne trouvez pas CustomizableThreadFactory? j'utilise jdk 1.7. une idée de ce qui me manque ici?
Kamran Shahid

@KamranShahid c'est une classe Spring Framework, vous devez utiliser Spring pour l'avoir
Adam Michalik

20

Il existe un RFE ouvert pour cela avec Oracle. D'après les commentaires de l'employé Oracle, il semble qu'ils ne comprennent pas le problème et ne le résoudront pas. C'est une de ces choses qui est très simple à prendre en charge dans le JDK (sans briser la compatibilité descendante), donc c'est une sorte de honte que le RFE soit mal compris.

Comme indiqué, vous devez implémenter votre propre ThreadFactory . Si vous ne voulez pas utiliser Guava ou Apache Commons uniquement à cette fin, je fournis ici une ThreadFactoryimplémentation que vous pouvez utiliser. Il est exactement similaire à ce que vous obtenez du JDK, à l'exception de la possibilité de définir le préfixe du nom du thread sur autre chose que "pool".

package org.demo.concurrency;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ThreadFactory with the ability to set the thread name prefix. 
 * This class is exactly similar to 
 * {@link java.util.concurrent.Executors#defaultThreadFactory()}
 * from JDK8, except for the thread naming feature.
 *
 * <p>
 * The factory creates threads that have names on the form
 * <i>prefix-N-thread-M</i>, where <i>prefix</i>
 * is a string provided in the constructor, <i>N</i> is the sequence number of
 * this factory, and <i>M</i> is the sequence number of the thread created 
 * by this factory.
 */
public class ThreadFactoryWithNamePrefix implements ThreadFactory {

    // Note:  The source code for this class was based entirely on 
    // Executors.DefaultThreadFactory class from the JDK8 source.
    // The only change made is the ability to configure the thread
    // name prefix.


    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    /**
     * Creates a new ThreadFactory where threads are created with a name prefix
     * of <code>prefix</code>.
     *
     * @param prefix Thread name prefix. Never use a value of "pool" as in that
     *      case you might as well have used
     *      {@link java.util.concurrent.Executors#defaultThreadFactory()}.
     */
    public ThreadFactoryWithNamePrefix(String prefix) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup()
                : Thread.currentThread().getThreadGroup();
        namePrefix = prefix + "-"
                + poolNumber.getAndIncrement()
                + "-thread-";
    }


    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY) {
            t.setPriority(Thread.NORM_PRIORITY);
        }
        return t;
    }
}

Lorsque vous souhaitez l'utiliser, vous profitez simplement du fait que toutes les Executorsméthodes vous permettent de fournir la vôtre ThreadFactory.

Ce

    Executors.newSingleThreadExecutor();

donnera un ExecutorService où les threads sont nommés pool-N-thread-Mmais en utilisant

    Executors.newSingleThreadExecutor(new ThreadFactoryWithNamePrefix("primecalc"));

vous obtiendrez un ExecutorService où les threads sont nommés primecalc-N-thread-M. Voila!


Vous avez manqué une parenthèse fermante dans votre dernier extrait
k.liakos

Juste une petite note que SonarLint / Qube préfère ne pas utiliser ThreadGroupen faveur de ThreadPoolExecutor.
Drakes

8
private class TaskThreadFactory implements ThreadFactory
{

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, "TASK_EXECUTION_THREAD");

        return t;
    }

}

Passez la ThreadFactory à un service d'exécution et vous êtes prêt à partir


8

Un moyen rapide et sale est d'utiliser Thread.currentThread().setName(myName);dans la run()méthode.


7

Étendre ThreadFactory

public interface ThreadFactory

Un objet qui crée de nouveaux threads à la demande. L'utilisation des usines de threads supprime le câblage des appels vers le nouveau thread, permettant aux applications d'utiliser des sous-classes de threads spéciales, des priorités, etc.

Thread newThread(Runnable r)

Construit un nouveau thread. Les implémentations peuvent également initialiser la priorité, le nom, l'état du démon, ThreadGroup, etc.

Exemple de code:

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy;

class SimpleThreadFactory implements ThreadFactory {
   String name;
   AtomicInteger threadNo = new AtomicInteger(0);

   public SimpleThreadFactory (String name){
       this.name = name;
   }
   public Thread newThread(Runnable r) {
     String threadName = name+":"+threadNo.incrementAndGet();
     System.out.println("threadName:"+threadName);
     return new Thread(r,threadName );
   }
   public static void main(String args[]){
        SimpleThreadFactory factory = new SimpleThreadFactory("Factory Thread");
        ThreadPoolExecutor executor= new ThreadPoolExecutor(1,1,60,
                    TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1),new ThreadPoolExecutor.DiscardPolicy());


        final ExecutorService executorService = Executors.newFixedThreadPool(5,factory);

        for ( int i=0; i < 100; i++){
            executorService.submit(new Runnable(){
                 public void run(){
                    System.out.println("Thread Name in Runnable:"+Thread.currentThread().getName());
                 }
            });
        }
        executorService.shutdown();
    }
 }

production:

java SimpleThreadFactory

thread no:1
thread no:2
Thread Name in Runnable:Factory Thread:1
Thread Name in Runnable:Factory Thread:2
thread no:3
thread no:4
Thread Name in Runnable:Factory Thread:3
Thread Name in Runnable:Factory Thread:4
thread no:5
Thread Name in Runnable:Factory Thread:5

....etc


1
Votre compteur de threads n'est pas thread-safe: vous devez utiliser un AtomicInteger.
Pino

Merci pour la suggestion. J'ai intégré votre suggestion.
Ravindra babu

5

Comme d'autres réponses l'ont déjà dit, vous pouvez créer et utiliser votre propre implémentation de l' java.util.concurrent.ThreadFactoryinterface (aucune bibliothèque externe requise). Je colle mon code ci-dessous car il est différent des réponses précédentes car il utilise la String.formatméthode et prend un nom de base pour les threads comme argument constructeur:

import java.util.concurrent.ThreadFactory;

public class NameableThreadFactory implements ThreadFactory{
    private int threadsNum;
    private final String namePattern;

    public NameableThreadFactory(String baseName){
        namePattern = baseName + "-%d";
    }

    @Override
    public Thread newThread(Runnable runnable){
        threadsNum++;
        return new Thread(runnable, String.format(namePattern, threadsNum));
    }    
}

Et ceci est un exemple d'utilisation:

ThreadFactory  threadFactory = new NameableThreadFactory("listenerThread");        
final ExecutorService executorService = Executors.newFixedThreadPool(5, threadFactory);

EDIT : rendre mon ThreadFactoryimplémentation thread-safe, merci à @mchernyakov de l' avoir signalé.
Même si nulle part dans la ThreadFactorydocumentation il n'est dit que ses implémentations doivent être thread-safe, le fait que le DefaultThreadFactorythread-safe soit un indice important:

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class NameableThreadFactory implements ThreadFactory{
    private final AtomicInteger threadsNum = new AtomicInteger();

    private final String namePattern;

    public NameableThreadFactory(String baseName){
        namePattern = baseName + "-%d";
    }

    @Override
    public Thread newThread(Runnable runnable){
        return new Thread(runnable, String.format(namePattern, threadsNum.addAndGet(1)));
    }    
}

1
Votre compteur de threads (threadsNum) n'est pas threadsafe, vous devez utiliser AtomicInteger.
mchernyakov

Merci de l'avoir signalé, @mchernyakov Je viens de modifier ma réponse en conséquence.
Víctor Gil

4

La solution Java de base que j'utilise pour décorer les usines existantes:

public class ThreadFactoryNameDecorator implements ThreadFactory {
    private final ThreadFactory defaultThreadFactory;
    private final String suffix;

    public ThreadFactoryNameDecorator(String suffix) {
        this(Executors.defaultThreadFactory(), suffix);
    }

    public ThreadFactoryNameDecorator(ThreadFactory threadFactory, String suffix) {
        this.defaultThreadFactory = threadFactory;
        this.suffix = suffix;
    }

    @Override
    public Thread newThread(Runnable task) {
        Thread thread = defaultThreadFactory.newThread(task);
        thread.setName(thread.getName() + "-" + suffix);
        return thread;
    }
}

En action:

Executors.newSingleThreadExecutor(new ThreadFactoryNameDecorator("foo"));

3
Executors.newSingleThreadExecutor(r -> new Thread(r, "someName")).submit(getJob());

Runnable getJob() {
        return () -> {
            // your job
        };
}

3

Vous pouvez écrire votre propre implémentation de ThreadFactory, en utilisant par exemple une implémentation existante (comme defaultThreadFactory) et changer le nom à la fin.

Exemple d'implémentation de ThreadFactory:

class ThreadFactoryWithCustomName implements ThreadFactory {
    private final ThreadFactory threadFactory;
    private final String name;

    public ThreadFactoryWithCustomName(final ThreadFactory threadFactory, final String name) {
        this.threadFactory = threadFactory;
        this.name = name;
    }

    @Override
    public Thread newThread(final Runnable r) {
        final Thread thread = threadFactory.newThread(r);
        thread.setName(name);
        return thread;
    }
}

Et l'utilisation:

Executors.newSingleThreadExecutor(new ThreadFactoryWithCustomName(
        Executors.defaultThreadFactory(),
        "customName")
    );

3

J'utilise pour faire la même chose comme ci-dessous (nécessite une guavabibliothèque):

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("SO-POOL-%d").build();
ExecutorService executorService = Executors.newFixedThreadPool(5,namedThreadFactory);

1
Il convient de noter que cela ThreadFactoryBuilderprovient de la bibliothèque Google Guava.
Craig Otis

3

Je trouve plus facile d'utiliser un lambda comme fabrique de threads si vous voulez simplement changer le nom d'un exécuteur de thread unique.

Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Your name"));

cela crée deux fils. Un nommé "Votre nom" et un autre "pool-N-thread-M"
Systemsplanet

@Systemsplanet Non, ce n'est pas le cas. Prendre un vidage de thread à partir d'un exemple minimal qui utilise l'exécuteur pour exécuter un thread qui dort affiche les threads suivants:main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
CamW

Hum, ça l'a fait quand je l'ai essayé. Il est logique que ce soit le cas puisque si vous lui passez un nouveau Runnable (), il crée un thread pour vous, et vous créez vous-même un thread.
Systemsplanet

J'espère que vous avez utilisé un ThreadPoolExecutor à la place ou que vous en avez utilisé un à d'autres fins. Ce code ne créera pas de thread "pool-N-thread-M". De plus, je ne pense pas que cela ait du sens. Votre déclaration "si vous lui passez un nouveau Runnable () il crée un thread pour vous" n'est pas correcte. Il utilise ce runnable pour créer un thread et le fait une fois parce que c'est un exécuteur à un seul thread. Un seul thread est créé.
CamW

2

Ceci est mon usine personnalisée fournissant des noms personnalisés pour les analyseurs de vidage de thread. Habituellement, je donne juste tf=nullpour réutiliser la fabrique de threads par défaut JVM. Ce site Web a une usine de threads plus avancée.

public class SimpleThreadFactory implements ThreadFactory {
    private ThreadFactory tf;
    private String nameSuffix;

    public SimpleThreadFactory (ThreadFactory tf, String nameSuffix) {
        this.tf = tf!=null ? tf : Executors.defaultThreadFactory();
        this.nameSuffix = nameSuffix; 
    }

    @Override public Thread newThread(Runnable task) {
        // default "pool-1-thread-1" to "pool-1-thread-1-myapp-MagicTask"
        Thread thread=tf.newThread(task);
        thread.setName(thread.getName()+"-"+nameSuffix);
        return thread;
    }
}

- - - - - 

ExecutorService es = Executors.newFixedThreadPool(4, new SimpleThreadFactory(null, "myapp-MagicTask") );

Pour votre commodité, il s'agit d'une boucle de vidage de thread à des fins de débogage.

    ThreadMXBean mxBean=ManagementFactory.getThreadMXBean();
    long[] tids = mxBean.getAllThreadIds();
    System.out.println("------------");
    System.out.println("ThreadCount="+tids.length);
    for(long tid : tids) {
        ThreadInfo mxInfo=mxBean.getThreadInfo(tid);
        if (mxInfo==null) {
            System.out.printf("%d %s\n", tid, "Thread not found");
        } else {
            System.out.printf("%d %s, state=%s, suspended=%d, lockowner=%d %s\n"
                    , mxInfo.getThreadId(), mxInfo.getThreadName()
                    , mxInfo.getThreadState().toString()
                    , mxInfo.isSuspended()?1:0
                    , mxInfo.getLockOwnerId(), mxInfo.getLockOwnerName()
            );
        }
    }

Cela a très bien fonctionné pour moi, un peu surpris de ne pas avoir beaucoup voté. Quoi qu'il en soit, applaudit.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.