Uso de ThreadFactory en Java

11 minutos de lectura

¿Alguien puede explicar brevemente CÓMO y CUÁNDO usar ThreadFactory? Un ejemplo con y sin usar ThreadFactory podría ser muy útil para comprender las diferencias.

¡Gracias!

Avatar de usuario de Boris Pavlović
Boris Pavlovic

Aquí hay un uso posible:

Suponga que tiene un ExecutorService que ejecuta su Runnable tareas de forma multihilo y, de vez en cuando, un hilo muere debido a una excepción no detectada. Supongamos también que desea registrar todas estas excepciones. ThreadFactory resuelve este problema al permitirle definir un registrador uniforme para excepciones no detectadas en el Runnable que el hilo estaba ejecutando:

ExecutorService executor = Executors.newSingleThreadExecutor(new LoggingThreadFactory());

executor.submit(new Runnable() {
   @Override
   public void run() {
      someObject.someMethodThatThrowsRuntimeException();
   }
});

LoggingThreadFactory se puede implementar como este:

public class LoggingThreadFactory implements ThreadFactory
{

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

        t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
        {
            @Override
            public void uncaughtException(Thread t, Throwable e)
            {
                LoggerFactory.getLogger(t.getName()).error(e.getMessage(), e);
            }
        });

        return t;
    }
}

los ThreadFactory interface es una interfaz flexible que permite al programador manejar excepciones no detectadas como se muestra arriba, pero también permite mucho más control sobre los detalles de creación de un Thread (como definir un patrón para el nombre del subproceso), lo que lo hace bastante útil para fines de depuración y entornos de producción por igual.

  • Perdónenme por este comentario después de muchos años, pero este ejemplo es engañoso. La excepción lanzada por tareas Runnable enviadas a través de ExecutorService.submit() no será capturada por UncaughtExceptionHandler, solo se manejará ExecutorService.execute()

    – AbD_Riz

    31/03/2018 a las 21:31


  • hola, ¿qué debo usar para subprocesos invocables? No puedo encontrar nada, se agradece la ayuda.

    – Think Tank

    29 oct 2019 a las 10:07

Avatar de usuario de Andreas Dolk
Andreas Dolk

El patrón de fábrica es un patrón de diseño de creación utilizado en el desarrollo de software para encapsular los procesos involucrados en la creación de objetos.

Supongamos que tenemos algunos subprocesos de trabajo para diferentes tareas y los queremos con nombres especiales (por ejemplo, con fines de depuración). Entonces podríamos implementar un ThreadFactory:

public class WorkerThreadFactory implements ThreadFactory {
   private int counter = 0;
   private String prefix = "";

   public WorkerThreadFactory(String prefix) {
     this.prefix = prefix;
   }

   public Thread newThread(Runnable r) {
     return new Thread(r, prefix + "-" + counter++);
   }
}

Si tuviera tal requisito, sería bastante difícil implementarlo sin un patrón de fábrica o constructor.


ThreadFactory es parte de la API de Java porque también lo usan otras clases. Entonces, el ejemplo anterior muestra por qué deberíamos usar ‘una fábrica para crear subprocesos’ en algunas ocasiones pero, por supuesto, no hay absolutamente ninguna necesidad de implementar java.util.concurrent.ThreadFactory para cumplir con esta tarea.

  • Ese es un ejemplo bastante malo ya que ThreadFactory usa ThreadGroup, ContextClassLoader y AccessControlContext de la persona que llama (newThread (Runnable) que generalmente es el hilo que produce resultados y los coloca en una cola). Al menos el ThreadGroup debe mantenerse como referencia en el c-tor y los hilos deben crearse con.

    – mejorsss

    18 de enero de 2011 a las 23:57

  • @bestsss – ThreadFactory es una interfaz, utiliza nada. El ejemplo simple está cerca del ejemplo proporcionado por la propia documentación de la clase y se centró en el patrón de fábrica. Siéntase libre de proporcionar un buen ejemplo en tu propia respuesta.

    – Andreas Dolk

    19 de enero de 2011 a las 6:38

  • el comentario no pretendía ser ofensivo y soy muy consciente de lo que está escrito en el documento de ThreadFactory. Executors.defaultThreadFactory() y Executors.privilegedThreadFactory() son buenos puntos de partida[esp the latter] (sin embargo, carecen de nombres sensatos), el documento de Executors.privilegedThreadFactory() también es bastante decente.

    – mejorsss

    19 de enero de 2011 a las 10:38


  • ¿No sería necesario que el contador fuera atómico o al menos volátil? De lo contrario, varios subprocesos podrían tener el mismo contador si se crean al mismo tiempo.

    – Marco

    18/08/2013 a las 17:50

  • @ Marcus volatile no garantiza atomicidad si dos subprocesos llamaran a newThread al mismo tiempo. el contador debe declararse como AtomicInteger.

    – Capitán Hastings

    8 de noviembre de 2014 a las 20:44

avatar de usuario de bestsss
mejorsss

Algunos trabajos internos

El tema está bastante bien cubierto excepto por algunos trabajos internos que no son fácilmente visibles. Al crear un subproceso con el constructor, el subproceso recién creado hereda los subprocesos actuales:

  • ThreadGroup (a menos que se suministre o System.getSecurityManager().getThreadGroup() devuelve arbitrario ThreadGroup) – El grupo de subprocesos por sí solo puede ser importante en algunos casos y puede resultar en una terminación o interrupción incorrecta del subproceso. los ThreadGroup permanecerá como controlador de excepciones predeterminado.
  • ContextClassLoader – en un entorno administrado, eso no debería ser un gran problema, ya que el entorno cambiará la CCL, pero si va a implementar eso, téngalo en cuenta. La fuga de la CCL de la persona que llama es bastante mala, al igual que el grupo de subprocesos (especialmente si el grupo de subprocesos es una subclase y no es directo). java.lang.ThreadGroup – necesita anular ThreadGroup.uncaughtException)
  • AccessControlContext – aquí, prácticamente no hay nada que hacer (excepto comenzar en un hilo dedicado) ya que el campo es solo para uso interno, y pocos sospechan que existe.
  • tamaño de pila (por lo general, no se especifica, pero puede ser divertido obtener un hilo con un tamaño de pila muy estrecho, según la persona que llama)
  • Prioridad: la mayoría de la gente lo conoce y tiende a establecerlo (más o menos)
  • estado del demonio: por lo general, eso no es muy importante y se nota fácilmente (si la aplicación simplemente desaparece)
  • Por último: el hilo hereda de la persona que llama InheritableThreadLocal – lo que puede (o no) dar lugar a algunas implicaciones. Nuevamente, no se puede hacer nada al respecto, además de generar el hilo en un hilo dedicado.

Dependiendo de la aplicación, es posible que los puntos anteriores no tengan ningún efecto, pero en algunos casos, algunos de ellos pueden provocar fugas de clase/recursos que son difíciles de detectar y muestran un comportamiento no determinista.


Eso haría una publicación extra larga, pero así que…

a continuación hay un código (con suerte) reutilizable para ThreadFactory implementación, se puede utilizar en entornos administrados para garantizar ThreadGroup (que puede limitar la prioridad o interrumpir hilos), ContextClassLoader, stacksize, etc. están establecidos (y/o pueden configurarse) y no se filtran. Si hay algún interés, puedo mostrar cómo tratar con herencia ThreadLocals o el acc heredado (que esencialmente puede filtrar la llamada classloader)

package bestsss.util;

import java.lang.Thread.UncaughtExceptionHandler;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadFactoryX implements ThreadFactory{
    //thread properties
    long stackSize;
    String pattern;
    ClassLoader ccl;
    ThreadGroup group;
    int priority;
    UncaughtExceptionHandler exceptionHandler;
    boolean daemon;

    private boolean configured;

    private boolean wrapRunnable;//if acc is present wrap or keep it
    protected final AccessControlContext acc;

    //thread creation counter
    protected final AtomicLong counter = new AtomicLong();

    public ThreadFactoryX(){        
        final Thread t = Thread.currentThread();
        ClassLoader loader;
    AccessControlContext acc = null;
    try{
        loader =  t.getContextClassLoader();
        if (System.getSecurityManager()!=null){
            acc = AccessController.getContext();//keep current permissions             
            acc.checkPermission(new RuntimePermission("setContextClassLoader"));
        }
    }catch(SecurityException _skip){
        //no permission
        loader =null;
        acc = null;
    }

    this.ccl = loader;
    this.acc = acc;
    this.priority = t.getPriority();    
    this.daemon = true;//Executors have it false by default

    this.wrapRunnable = true;//by default wrap if acc is present (+SecurityManager)

    //default pattern - caller className
    StackTraceElement[] stack =  new Exception().getStackTrace();    
    pattern(stack.length>1?getOuterClassName(stack[1].getClassName()):"ThreadFactoryX", true);     
    }

    public ThreadFactory finishConfig(){
        configured = true;
        counter.addAndGet(0);//write fence "w/o" volatile
        return this;
    }

    public long getCreatedThreadsCount(){
        return counter.get();
    }

    protected void assertConfigurable(){
        if (configured)
            throw new IllegalStateException("already configured");
    }

    private static String getOuterClassName(String className){
        int idx = className.lastIndexOf('.')+1;
        className = className.substring(idx);//remove package
        idx = className.indexOf('$');
        if (idx<=0){
            return className;//handle classes starting w/ $
        }       
        return className.substring(0,idx);//assume inner class

    }

    @Override
    public Thread newThread(Runnable r) {
        configured = true;
        final Thread t = new Thread(group, wrapRunnable(r), composeName(r), stackSize);
        t.setPriority(priority);
        t.setDaemon(daemon);
        t.setUncaughtExceptionHandler(exceptionHandler);//securityException only if in the main group, shall be safe here
        //funny moment Thread.getUncaughtExceptionHandler() has a race.. badz (can throw NPE)

        applyCCL
        return t;
    }

    private void applyCCL(final Thread t) {
        if (ccl!=null){//use factory creator ACC for setContextClassLoader
            AccessController.doPrivileged(new PrivilegedAction<Object>(){
                @Override
                public Object run() {
                    t.setContextClassLoader(ccl);
                    return null;
                }                               
            }, acc);        
        }
    }
    private Runnable wrapRunnable(final Runnable r){
        if (acc==null || !wrapRunnable){
            return r;
        }
        Runnable result = new Runnable(){
            public void run(){
                AccessController.doPrivileged(new PrivilegedAction<Object>(){
                    @Override
                    public Object run() {
                        r.run();
                        return null;
                    }                               
                }, acc);
            }
        };
        return result;      
    }


    protected String composeName(Runnable r) {
        return String.format(pattern, counter.incrementAndGet(), System.currentTimeMillis());
    }   


    //standard setters allowing chaining, feel free to add normal setXXX    
    public ThreadFactoryX pattern(String patten, boolean appendFormat){
        assertConfigurable();
        if (appendFormat){
            patten+=": %d @ %tF %<tT";//counter + creation time
        }
        this.pattern = patten;
        return this;
    }


    public ThreadFactoryX daemon(boolean daemon){
        assertConfigurable();
        this.daemon = daemon;
        return this;
    }

    public ThreadFactoryX priority(int priority){
        assertConfigurable();
        if (priority<Thread.MIN_PRIORITY || priority>Thread.MAX_PRIORITY){//check before actual creation
            throw new IllegalArgumentException("priority: "+priority);
        }
        this.priority = priority;
        return this;
    }

    public ThreadFactoryX stackSize(long stackSize){
        assertConfigurable();
        this.stackSize = stackSize;
        return this;
    }


    public ThreadFactoryX threadGroup(ThreadGroup group){
        assertConfigurable();
        this.group= group;
        return this;        
    }

    public ThreadFactoryX exceptionHandler(UncaughtExceptionHandler exceptionHandler){
        assertConfigurable();
        this.exceptionHandler= exceptionHandler;
        return this;                
    }

    public ThreadFactoryX wrapRunnable(boolean wrapRunnable){
        assertConfigurable();
        this.wrapRunnable= wrapRunnable;
        return this;                        
    }

    public ThreadFactoryX ccl(ClassLoader ccl){
        assertConfigurable();
        this.ccl = ccl;
        return this;
    }
}

También un uso muy simple:

ThreadFactory factory = new TreadFactoryX().priority(3).stackSize(0).wrapRunnable(false).pattern("Socket workers", true).
daemon(false).finishConfig();

  • Esto parece similar a ThreadFactoryBuilder de Google Guava. Aunque no creo que manejen el ClassLoader como tú. Ver guava-libraries.googlecode.com/svn/tags/release05/javadoc/com/…

    – Darwyn

    11 de julio de 2012 a las 3:45

  • @Darwyn, no sé nada sobre Guava (en cierto modo no lo uso y no he estudiado su código), el código anterior proviene por completo de la experiencia personal. En realidad, el manejo del cargador de clases es perjudicial para cualquier middleware, ni siquiera puedo enfatizarlo lo suficiente.

    – mejorsss

    11 de julio de 2012 a las 18:46


  • hola, ¿qué debo usar para subprocesos invocables? No puedo encontrar nada, se agradece la ayuda.

    – Think Tank

    29 de octubre de 2019 a las 10:08

En mi humilde opinión, la función más importante de un ThreadFactory es nombrar hilos algo útil. Tener subprocesos en un stacktrace llamado pool-1-thread-2 o peor Thread-12 es un completo dolor a la hora de diagnosticar problemas.

Por supuesto, tener un ThreadGroupel estado y la prioridad del demonio también son útiles.

Siempre es una buena práctica utilizar la fábrica de hilos personalizados. Las fábricas predeterminadas no son muy útiles. Debe usar una fábrica personalizada por las siguientes razones:

  1. Para tener nombres de hilos personalizados
  2. Para elegir entre tipos de hilo
  3. Para elegir la prioridad del subproceso
  4. Para manejar excepciones no detectadas

Revisa esta publicación:
http://wilddiary.com/understanding-java-threadfactory-creating-custom-thread-factories/

Como lo menciona “InsertNickHere”, tendrá que entender el Patrón de fábrica.

Un buen ejemplo para el uso de ThreadFactory es el ThreadPoolExecutor: El Ejecutor creará Subprocesos si es necesario y se encargará de la agrupación. Si desea intervenir en el proceso de creación y dar nombres especiales a los subprocesos creados, o asignarlos a un grupo de subprocesos, puede crear una fábrica de subprocesos para ese propósito y dársela al ejecutor.

es un poco COI-estilo.

Avatar de usuario de Premraj
Premraj

Uso de ThreadFactory en Java

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

La implementación más simple de esta interfaz es simplemente:

class SimpleThreadFactory implements ThreadFactory {
   public Thread newThread(Runnable r) {
     return new Thread(r);
   }
 }

DefaultThreadFactory de ThreadPoolExecutor.java

static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

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

Fuente

¿Ha sido útil esta solución?