Nombrar hilos y grupos de hilos de ExecutorService


228

Digamos que tengo una aplicación que utiliza el Executorframework como tal

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

Cuando ejecuto esta aplicación en el depurador, un hilo se crea con el siguiente nombre (por defecto): Thread[pool-1-thread-1]. Como puede ver, esto no es terriblemente útil y, por lo que puedo decir, el Executormarco no proporciona una manera fácil de nombrar los hilos o grupos de hilos creados.

Entonces, ¿cómo se hace para proporcionar nombres para los hilos / grupos de hilos? Por ejemplo, Thread[FooPool-FooThread].

Respuestas:


118

Podrías proporcionar un ThreadFactorya newSingleThreadScheduledExecutor(ThreadFactory threadFactory). La fábrica será responsable de crear hilos y podrá nombrarlos.

Para citar el Javadoc :

Crear nuevos hilos

Se crean nuevos hilos usando a ThreadFactory. Si no se especifica lo contrario, Executors.defaultThreadFactory()se utiliza un, que crea subprocesos para que todos estén en el mismo ThreadGroupy con la misma NORM_PRIORITYprioridad y estado de no demonio. Al proporcionar un diferente ThreadFactory, puede alterar el nombre del hilo, el grupo de hilos, la prioridad, el estado del demonio, etc. Si ThreadFactoryno se puede crear un hilo cuando se le pregunta al devolver nulo desde newThread, el ejecutor continuará, pero es posible que no pueda ejecutar ninguna tarea


283

La guayaba casi siempre tiene lo que necesitas .

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

y pasárselo a tu ExecutorService.


3
¡Eso es fantástico!
Martin Vseticka

25
¡Eso es triste! :-(
exic

No estoy seguro de dónde encontrar "guayaba". Hay muchas partes en la guayaba de Google y hay docenas de bibliotecas con el mismo nombre. Supongo que te refieres a search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . ¿Está bien? El enlace que proporciona sugiere que es de Google, pero Google también tiene alrededor de media docena de artefactos en Maven / Sonatype llamados "guayaba".
Jason

@ Jason: si está escribiendo un proyecto Java no trivial, lo más probable es que ya tenga la guayaba como dependencia. Y aquí está: github.com/google/guava
pathikrit

@pathikrit, gracias! Creo que necesito estudiar más sobre la guayaba :-)
Jason

95

Puede intentar proporcionar su propia fábrica de hilos, que creará hilos con los nombres apropiados. Aquí hay un ejemplo:

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

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

58

También puede cambiar el nombre de su hilo después, mientras se ejecuta el hilo:

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

Eso podría ser interesante si, por ejemplo, está utilizando el mismo ThreadFactory para diferentes tipos de tareas.


77
Esto funcionó bien porque, como lo describió FlorianT, tengo muchos tipos diferentes de hilos y no quería tener que crear múltiples objetos ThreadFactory solo por el nombre. Llamé a Thread.currentThread (). SetName ("FooName"); como la primera línea en cada método run ().
Robin Zimmermann

55
Un pequeño problema con esto es cuando se produce el comportamiento de falla se describe en los documentos: (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 el ExecutorService reemplaza el hilo, ThreadFactory lo nombrará. Por otra parte, ver desaparecer el nombre durante la depuración podría ser un indicador útil.
sethro

Simplemente excelente! Gracias.
pregunta el

1
Como dice la otra respuesta, este es un método rápido y sucio para establecer el nombre, y si lo haces con múltiples hilos, ¡todos tendrán el mismo nombre!
Tano

Es posible que desee volver a establecer el nombre del subproceso en original al salir, ya que puede retener el nombre incluso si está trabajando en diferentes tareas no relacionadas.
Dustin K

51

El BasicThreadFactoryde Apache Commons-lang también es útil para proporcionar el comportamiento de nombres. En lugar de escribir una clase interna anónima, puede usar el generador para nombrar los hilos como desee. Aquí está el ejemplo de los 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 está utilizando Spring, hay CustomizableThreadFactoryuna opción para establecer un prefijo de nombre de hilo.

Ejemplo:

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

Alternativamente, puede crear su ExecutorServicecomo un Spring bean usando ThreadPoolExecutorFactoryBean, luego los hilos se nombrarán con el beanName-prefijo.

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

En el ejemplo anterior, los hilos se nombrarán con myExecutor-prefijo. Puede establecer el prefijo explícitamente en un valor diferente (p. Ej. "myPool-") Configurando executorFactoryBean.setThreadNamePrefix("myPool-")el bean de fábrica.


no puede encontrar CustomizableThreadFactory? Estoy usando jdk 1.7. alguna idea de lo que me estoy perdiendo aquí?
Kamran Shahid

@KamranShahid esta es una clase Spring Framework, debes usar Spring para tenerla
Adam Michalik

20

Hay un RFE abierto para esto con Oracle. Según los comentarios del empleado de Oracle, parece que no entienden el problema y no lo solucionarán. Es una de estas cosas que es muy fácil de admitir en el JDK (sin romper la compatibilidad con versiones anteriores), por lo que es una pena que el RFE se malinterprete.

Como se señaló, debe implementar su propia ThreadFactory . Si no desea obtener Guava o Apache Commons solo para este propósito, le proporciono aquí una ThreadFactoryimplementación que puede usar. Es exactamente similar a lo que obtienes del JDK, excepto por la capacidad de establecer el prefijo del nombre del hilo en algo más 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;
    }
}

Cuando quiera usarlo, simplemente aproveche el hecho de que todos los Executorsmétodos le permiten proporcionar el suyo propio ThreadFactory.

Esta

    Executors.newSingleThreadExecutor();

dará un ExecutorService donde se nombran hilos pool-N-thread-Mpero usando

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

obtendrá un ExecutorService donde se nombran los hilos primecalc-N-thread-M. Voila!


Te has perdido un paréntesis de cierre en tu último fragmento
k.liakos

Solo una nota rápida que SonarLint / Qube prefiere no usar ThreadGroupa favor ThreadPoolExecutor.
Drakes

8
private class TaskThreadFactory implements ThreadFactory
{

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

        return t;
    }

}

Pase ThreadFactory a un servicio de ejecutor y estará listo para comenzar.


8

Una forma rápida y sucia es usar Thread.currentThread().setName(myName);el run()método.


7

Extender ThreadFactory

public interface ThreadFactory

Un objeto que crea nuevos hilos a pedido. El uso de fábricas de hilos elimina el cableado de las llamadas a nuevos hilos, permitiendo que las aplicaciones usen subclases de hilos especiales, prioridades, etc.

Thread newThread(Runnable r)

Construye un nuevo hilo. Las implementaciones también pueden inicializar prioridad, nombre, estado del demonio, ThreadGroup, etc.

Código de muestra:

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

salida:

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
El contador de subprocesos no es seguro para subprocesos: debe usar un AtomicInteger.
Pino

Gracias por la sugerencia He incorporado tu sugerencia.
Ravindra babu

5

Como ya se dijo en otras respuestas, puede crear y usar su propia implementación de la java.util.concurrent.ThreadFactoryinterfaz (no se requieren bibliotecas externas). Estoy pegando mi código a continuación porque es diferente a las respuestas anteriores, ya que usa el String.formatmétodo y toma un nombre base para los hilos como argumento de constructor:

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

Y este es un ejemplo de uso:

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

EDITAR : hacer que mi ThreadFactoryimplementación sea segura para subprocesos , gracias a @mchernyakov por señalarlo.
Aunque en ninguna parte de la ThreadFactorydocumentación se dice que sus implementaciones deben ser seguras para subprocesos, el hecho de que DefaultThreadFactoryes seguro para subprocesos es una gran pista:

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
El contador de subprocesos (threadsNum) no es seguro para subprocesos, debe usar AtomicInteger.
mchernyakov

Gracias por señalarlo, @mchernyakov. Acabo de editar mi respuesta en consecuencia.
Víctor Gil

4

La solución central de Java que utilizo 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;
    }
}

En acción:

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

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

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

3

Puede escribir su propia implementación de ThreadFactory, utilizando, por ejemplo, alguna implementación existente (como defaultThreadFactory) y cambiar el nombre al final.

Ejemplo de implementación 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;
    }
}

Y uso:

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

3

Solía ​​hacer lo mismo que a continuación (requiere guavabiblioteca):

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

1
Vale la pena señalar que ThreadFactoryBuilderes de la biblioteca Google Guava.
Craig Otis

3

Me resulta más fácil usar una lambda como fábrica de hilos si solo desea cambiar el nombre de un solo ejecutor de hilos.

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

Esto crea dos hilos. Uno llamado "Tu nombre" y otro "pool-N-thread-M"
Systemsplanet

@Systemsplanet No, no lo hace. Tomar un volcado de subprocesos de un ejemplo mínimo que utiliza el ejecutor para ejecutar un subproceso que duerme muestra los siguientes subprocesos:main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
CamW

Hum, lo hizo cuando lo probé. Tiene sentido que lo haga, ya que si le pasa un nuevo Runnable () crea un hilo para usted, y usted mismo está creando un hilo.
Systemsplanet

Espero que haya utilizado un ThreadPoolExecutor en su lugar o que haya ejecutado uno para algún otro propósito. Este código no creará un subproceso "pool-N-thread-M". Además, no creo que tenga sentido que lo haga. Su afirmación "si le pasa un nuevo Runnable () crea un hilo para usted" no es correcta. Utiliza ese ejecutable para crear un hilo y lo hace una vez porque es un ejecutor de un solo hilo. Solo se crea 1 hilo.
CamW

2

Esta es mi fábrica personalizada que proporciona nombres personalizados para analizadores de volcado de subprocesos. Por lo general, solo doy tf=nullpara reutilizar la fábrica de hilos predeterminada de JVM. Este sitio web tiene una fábrica de hilos más avanzada.

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 su comodidad, este es un bucle de volcado de subprocesos para fines de depuración.

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

Esto funcionó muy bien para mí, algo sorprendido de que no haya votado demasiado. De cualquier manera aplausos.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.