Nomeando threads e pools de threads do ExecutorService


228

Digamos que eu tenho um aplicativo que utiliza a Executorestrutura como tal

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

Quando eu executar esta aplicação no depurador, um thread é criado com o seguinte (default) Nome: Thread[pool-1-thread-1]. Como você pode ver, isso não é muito útil e, até onde eu sei, o Executorframework não fornece uma maneira fácil de nomear os threads ou pools de threads criados.

Então, como fornecer nomes para os threads / pools de threads? Por exemplo Thread[FooPool-FooThread],.

Respostas:


118

Você poderia fornecer um ThreadFactorypara newSingleThreadScheduledExecutor(ThreadFactory threadFactory). A fábrica será responsável pela criação de threads e poderá nomeá-los.

Para citar o Javadoc :

Criando Novos Threads

Novos threads são criados usando a ThreadFactory. Se não for especificado de outra forma, a Executors.defaultThreadFactory()é usado, que cria encadeamentos para que todos estejam no mesmo ThreadGroupe com a mesma NORM_PRIORITYprioridade e status de não daemon. Ao fornecer um diferente ThreadFactory, você pode alterar o nome, o grupo, a prioridade, o status do daemon, etc. Se um ThreadFactorynão conseguir criar um thread quando solicitado, retornando nulo de newThread, o executor continuará, mas poderá não conseguir executar nenhuma tarefa


283

Goiaba quase sempre tem o que você precisa .

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

e passe para o seu ExecutorService.


3
Isso é fantástico!
Martin Vseticka 14/10

25
Isso é triste! :-(
exic 14/09/18

Não sei onde encontrar "goiaba". Existem muitas partes no Google Guava e existem dezenas de bibliotecas com o mesmo nome. Suponho que você queira dizer search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . Isso está certo? O link que você fornece sugere que é do Google, mas o Google também possui cerca de meia dúzia de artefatos no Maven / Sonatype chamado "goiaba".
Jason

@ Jason - Se você estiver escrevendo um projeto Java não trivial, provavelmente já deve ter a goiaba como uma dependência. E aqui está: github.com/google/guava
pathikrit

@pathikrit, obrigado! Acho que preciso estudar mais sobre a goiaba :-)
Jason

95

Você pode tentar fornecer sua própria fábrica de threads, que criará threads com nomes apropriados. Aqui está um exemplo:

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

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

58

Você também pode alterar o nome do seu segmento posteriormente, enquanto o segmento é executado:

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

Isso pode ser interessante se, por exemplo, você estiver usando o mesmo ThreadFactory para diferentes tipos de tarefas.


7
Isso funcionou bem porque, como FlorianT descreveu, eu tenho muitos tipos diferentes de threads e não queria criar vários objetos ThreadFactory apenas para o nome. Eu chamei Thread.currentThread (). SetName ("FooName"); como a primeira linha em cada método run ().
Robin Zimmermann

5
Uma questão menor com isso é quando o comportamento falha descrito na documentação ocorre: (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.). Se o ExecutorService substituir o segmento, ele será nomeado pelo ThreadFactory. Por outro lado, ver o nome desaparecer durante a depuração pode ser um indicador útil.
Sethro

Simplesmente soberbo! Obrigado.
ASGs

1
Como a outra resposta diz, este é um método rápido e sujo para definir o nome e, se você fizer isso com vários threads, todos terão o mesmo nome!
Tano 22/06

Pode ser necessário definir o nome do encadeamento de volta ao original na saída, porque ele pode reter o nome mesmo se estiver trabalhando em diferentes tarefas não relacionadas.
Dustin K

51

O BasicThreadFactoryfrom apache commons-lang também é útil para fornecer o comportamento de nomeação. Em vez de escrever uma classe interna anônima, você pode usar o Construtor para nomear os threads como desejar. Aqui está o exemplo dos 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

Se você estiver usando o Spring, CustomizableThreadFactorypoderá definir um prefixo de nome de thread.

Exemplo:

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

Como alternativa, você pode criar seu ExecutorServicecomo um bean Spring usando ThreadPoolExecutorFactoryBean- todos os threads serão nomeados com o beanName-prefixo.

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

No exemplo acima, os segmentos serão nomeados com myExecutor-prefixo. Você pode definir o prefixo explicitamente para um valor diferente (por exemplo "myPool-") configurando executorFactoryBean.setThreadNamePrefix("myPool-")no bean de fábrica.


não consegue encontrar CustomizableThreadFactory? Estou usando o jdk 1.7. alguma idéia do que estou perdendo aqui?
Kamran Shahid

@KamranShahid esta é uma classe do Spring Framework, você deve usar o Spring para tê-lo
Adam Michalik 23/01

20

Há uma RFE aberta para isso com a Oracle. Pelos comentários do funcionário da Oracle, parece que eles não entendem o problema e não corrigem. É uma dessas coisas que é simples de suportar no JDK (sem quebrar a compatibilidade com versões anteriores), por isso é uma pena que a RFE seja mal compreendida.

Conforme indicado, você precisa implementar seu próprio ThreadFactory . Se você não deseja obter o Goiaba ou o Apache Commons apenas para esse fim, forneço aqui uma ThreadFactoryimplementação que você pode usar. É exatamente semelhante ao que você obtém do JDK, exceto pela capacidade de definir o prefixo do nome do encadeamento para algo diferente de "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;
    }
}

Quando você deseja usá-lo, simplesmente tira proveito do fato de que todos os Executorsmétodos permitem que você forneça o seu ThreadFactory.

este

    Executors.newSingleThreadExecutor();

dará um ExecutorService onde os threads são nomeados, pool-N-thread-Mmas usando

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

você receberá um ExecutorService em que os threads são nomeados primecalc-N-thread-M. Voila!


Você perdeu um parêntese de fechamento em seu último trecho de
código

Apenas uma observação rápida de que o SonarLint / Qube prefere não usar a ThreadGroupfavor ThreadPoolExecutor.
quer

8
private class TaskThreadFactory implements ThreadFactory
{

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

        return t;
    }

}

Passe o ThreadFactory para um executorservice e você estará pronto


8

Uma maneira rápida e suja é usar Thread.currentThread().setName(myName);no run()método.


7

Estender ThreadFactory

public interface ThreadFactory

Um objeto que cria novos threads sob demanda. O uso de fábricas de encadeamento remove a ligação das chamadas para o novo encadeamento, permitindo que os aplicativos usem subclasses, prioridades, etc.

Thread newThread(Runnable r)

Constrói um novo segmento. As implementações também podem inicializar prioridade, nome, status do daemon, ThreadGroup etc.

Código de amostra:

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

resultado:

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
O contador de threads não é seguro para threads: você deve usar um AtomicInteger.
Pino

Obrigado pela sugestão. Eu incorporei sua sugestão.
Ravindra babu

5

Como outras respostas já disseram, você pode criar e usar sua própria implementação da java.util.concurrent.ThreadFactoryinterface (não é necessária nenhuma biblioteca externa). Estou colando meu código abaixo porque é diferente das respostas anteriores, pois usa o String.formatmétodo e usa um nome de base para os segmentos como um argumento construtor:

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

E este é um exemplo de uso:

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

EDIT : tornando minha ThreadFactoryimplementação segura para threads , obrigado a @mchernyakov por apontá-la.
Embora em nenhuma parte da ThreadFactorydocumentação se diga que suas implementações devam ser seguras contra threads, o fato de DefaultThreadFactoryser seguro é uma grande dica:

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
Seu contador de threads (threadsNum) não é seguro para threads, você deve usar AtomicInteger.
Mkernyakov

Obrigado por apontar, @mchernyakov Acabei de editar minha resposta de acordo.
Víctor Gil

4

A principal solução Java doméstica que eu uso para decorar fábricas existentes:

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

Em ação:

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

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

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

3

Você pode escrever sua própria implementação do ThreadFactory, usando, por exemplo, alguma implementação existente (como defaultThreadFactory) e alterar o nome no final.

Exemplo de implementação do 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;
    }
}

E uso:

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

3

Eu uso para fazer o mesmo como abaixo (requer guavabiblioteca):

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

1
Vale ressaltar que ThreadFactoryBuilderé da biblioteca do Google Guava.
Craig Otis

3

Acho mais fácil usar um lambda como uma fábrica de threads, se você quiser apenas alterar o nome de um executor de thread único.

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

isso cria dois threads. Um chamado "Nome" e outro "pool-N-thread-M"
Systemsplanet

@Systemsplanet Não, não é. Tomando um despejo de encadeamento de um exemplo mínimo, que usa o executor para executar um encadeamento que dorme, mostra os seguintes encadeamentos:main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
CamW

Hum, aconteceu quando eu tentei. Faz sentido que, desde que, se você passar um novo Runnable (), ele criará um thread para você, e você mesmo estiver criando um thread.
Systemsplanet

Espero que você tenha usado um ThreadPoolExecutor ou que tenha um em execução para algum outro propósito. Este código não criará um segmento "pool-N-thread-M". Além disso, não acredito que faça sentido. Sua declaração "se você passar um novo Runnable (), cria um thread para você" não está correta. Ele usa esse executável para criar um thread e faz isso uma vez porque é um executor de thread único. Somente 1 thread é criado.
CamW

2

Esta é minha fábrica personalizada, fornecendo nomes personalizados para analisadores de despejo de segmentos. Normalmente, dou apenas tf=nullpara reutilizar a fábrica de threads padrão da JVM. Este site possui uma fábrica de threads mais avançada.

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

Para sua conveniência, este é um loop de despejo de threads para fins de depuração.

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

Isso funcionou muito bem para mim, um pouco surpreso por não ter sido muito votado. De qualquer maneira, aplausos.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.