Использование ThreadFactory в Java

41

Может кто-нибудь кратко объяснить, КАК И КОГДА использовать ThreadFactory? Пример с использованием и без использования ThreadFactory может быть действительно полезен для понимания различий.

Спасибо!

Теги:
multithreading
concurrency

10 ответов

35
Лучший ответ

Шаблон factory представляет собой шаблон создания, используемый в разработке программного обеспечения для инкапсуляции процессов, связанных с созданием объектов.

Предположим, что у нас есть некоторые рабочие потоки для разных задач и они нуждаются в специальных именах (например, для целей отладки). Таким образом, мы могли бы реализовать 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++);
   }
}

Если бы у вас было такое требование, было бы довольно сложно реализовать его без шаблона factory или построителя.


ThreadFactory является частью Java API, потому что он также используется другими классами. Таким образом, приведенный выше пример показывает, почему мы должны использовать 'a factory для создания потоков в некоторых случаях, но, конечно, нет абсолютно никакой необходимости реализовывать java.util.concurrent.ThreadFactory для выполнения этой задачи.

  • 0
    Это довольно плохой пример, так как ThreadFactory использует ThreadGroup, ContextClassLoader и AccessControlContext вызывающей стороны (newThread (Runnable), который обычно является потоком, который производит результаты и отбрасывает их в очередь). По крайней мере, ThreadGroup должна быть сохранена как ссылка в c-tor, а потоки должны быть созданы с помощью.
  • 3
    @bestsss - ThreadFactory - это интерфейс, он ничего не использует. Простой пример близок к примеру, предоставленному собственной документацией класса, и основное внимание было уделено фабричному шаблону. Не стесняйтесь привести хороший пример в своем ответе.
Показать ещё 4 комментария
51

Здесь одно возможное использование. Если у вас есть служба-исполнитель, которая выполняет ваши запущенные задачи многопоточным способом, и время от времени ваш поток умирает от неперехваченного исключения. Предположим, что вы не будете регистрировать все эти исключения. ThreadFactory решает эту проблему:

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

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

LoggingThreadFactory может быть реализован следующим образом:

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;
    }
}
  • 2
    Прошу прощения за этот комментарий через много лет, но этот пример вводит в заблуждение. Исключение, создаваемое задачами Runnable, переданными через ExecutorService.submit (), не будет перехвачено UncaughtExceptionHandler, будет обрабатываться только ExecutorService.execute ()
14

Некоторые внутренние операции

Тема довольно хорошо освещена, за исключением некоторых внутренних работ, которые не так легко увидеть. При создании потока w/конструктор вновь созданный поток наследует текущие потоки:

  • ThreadGroup (если не указано, или System.getSecurityManager(). getThreadGroup() возвращает произвольную ThreadGroup). В некоторых случаях группа потоков в ее собственном праве может быть важной и может привести к неправильному завершению/прерыванию потока. ThreadGroup будет выступать в качестве обработчика исключений по умолчанию.
  • ContextClassLoader - в управляемой среде, которая не должна быть большой проблемой, поскольку среда должна переключать CCL, но если вы должны это реализовать, не забудьте. Утечка вызывающего CCL довольно плохая, так же как и группа потоков (особенно если threadGroup - это некоторый подкласс, а не прямой java.lang.ThreadGroup - необходимо переопределить ThreadGroup.uncaughtException)
  • AccessControlContext - здесь практически ничего не нужно делать (кроме запуска в выделенном потоке), поскольку поле предназначено только для внутреннего использования, и мало кто даже подозревает существование.
  • размер стека (обычно он не указан, но он может использовать для получения потока с очень узким размером стека на основе вызывающего)
  • приоритет - большинство людей знают и склонны устанавливать его (более или менее)
  • статус демона - обычно это не очень важно и легко примечательно (если приложение просто исчезает)
  • Наконец: поток наследует вызывающего абонента InheritableThreadLocal - который может (или не может) привести к некоторым последствиям. Снова ничего не может быть сделано, кроме того, что порождает поток в выделенный поток.

В зависимости от приложения указанные выше точки могут вообще не иметь эффекта, но в некоторых случаях некоторые из них могут привести к утечкам класса/ресурсов, которые трудно обнаружить и проявить не детерминированное поведение.


Это сделает дополнительный длинный пост, но так...

Ниже приведен некоторый (надеюсь) многоразовый код для реализации ThreadFactory, он может использоваться в управляемых средах для обеспечения правильной ThreadGroup (которая может ограничивать потоки приоритета или прерывания), ContextClassLoader, стекизировать и т.д. set (и/или может быть настроен) и не просочиться. Если есть какой-либо интерес, я могу показать, как обращаться с унаследованными ThreadLocals или унаследованным acc (который по существу может пропустить вызывающий загрузчик классов)

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

Также очень простое использование:

ThreadFactory factory = new TreadFactoryX().priority(3).stackSize(0).wrapRunnable(false).pattern("Socket workers", true).
daemon(false).finishConfig();
  • 0
    Это похоже на GoogleFava ThreadFactoryBuilder. Хотя я не верю, что они обрабатывают ClassLoader, как вы. См. Guava-libraries.googlecode.com/svn/tags/release05/javadoc/com/…
  • 0
    @Darwyn, я ничего не знаю о Гуаве (в смысле, я не использую его, и я не изучал его код), приведенный выше код полностью основан на личном опыте. На самом деле обработка загрузчика классов пагубна для любого промежуточного программного обеспечения, я даже не могу не подчеркнуть этого.
3

IMHO единственная самая важная функция ThreadFactory - это именовать потоки что-то полезное. Наличие потоков в stacktrace с именем pool-1-thread-2 или хуже Thread-12 является полной болью при диагностике проблем.

Конечно, наличие ThreadGroup, статус и приоритет демона также полезны.

2

Всегда полезно использовать пользовательский поток factory. Заводы по умолчанию не очень полезны. Вы должны использовать пользовательский factory по следующим причинам:

  • Чтобы иметь собственные имена потоков
  • Выбор типов потоков
  • Выбор приоритета потока
  • Для обработки исключений uncaught

Отметьте это сообщение: http://wilddiary.com/understanding-java-threadfactory-creating-custom-thread-factories/

2

Как упоминалось в "InsertNickHere", вам нужно будет понять Factory Pattern.

Хорошим примером использования ThreadFactory является ThreadPoolExecutor: Исполнитель будет создавать темы, если это необходимо, и позаботиться об объединении. Если вы хотите войти в процесс создания и предоставить специальные имена созданным потокам или назначить их в ThreadGroup, вы можете создать ThreadFactory для этой цели и передать его Исполнителю.

Немного IoC-стиль.

1

Использование ThreadFactory в Java

Объект, который создает новые потоки по требованию. Использование фабрик потоков удаляет жесткие вызовы на new Thread, позволяя приложениям использовать специальные подклассы потоков, приоритеты и т.д.

Простейшая реализация этого интерфейса:

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

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

Источник

1

ThreadFactory - это интерфейс с одним методом public abstract java.lang.Thread newThread(java.lang.Runnable arg0);

Его использование зависит от вашего требования. Предположим, вы хотите, чтобы определенная функциональность всегда создавала потоки Daemon. Вы можете легко достичь этого с помощью ThreadFactory.

Следующий код предназначен только для того, чтобы рассказать о фундаментальном. Он не выполняет никаких конкретных функций.

package TestClasses;
import java.util.concurrent.ThreadFactory;
public class ThreadFactoryEx implements ThreadFactory{
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

package TestClasses;
import java.util.concurrent.ThreadPoolExecutor;
public class RunnableEx implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) {
            System.out.println("in a loop" + i + "times");
        }
    }
}


package TestClasses;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Thread1 {
    public static void main(String[] args) {
        ExecutorService exe = Executors.newCachedThreadPool(new ThreadFactoryEx());
        for (int i = 0; i < 4; i++) {
            exe.execute(new RunnableEx());
        }
    }
}
0

ThreadFactory будет полезно

  • для установки более описательного имени потока
  • чтобы установить статус демона нити
  • для установки приоритета потока

Вы можете использовать ThreadFactoryBuilder из google Guava lib для создания ThreadFactory, как этот

ThreadFactory threadFactory = new ThreadFactoryBuilder()
        .setNameFormat("MyThreadPool-Worker-%d")
        .setDaemon(true)
        .build();
0

Посмотрите VerboseThreads (реализует ThreadFactory) из jcabi-log. Эта реализация делает Thread, что журналы исключений, когда они выбрасываются из них. Очень полезный класс, когда вам нужно посмотреть, когда и почему ваши нити умирают.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню