Именование потоков и пулов потоков ExecutorService

228

Допустим, у меня есть приложение, которое использует Executorфреймворк как таковой

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

Когда я запускаю это приложение в отладчик, поток создается с именем следующей ( по умолчанию): Thread[pool-1-thread-1]. Как видите, это не очень полезно, и, насколько я могу судить, Executorинфраструктура не предоставляет простой способ назвать созданные потоки или пулы потоков.

Итак, как можно обеспечить имена для потоков / пулов потоков? Так , например, Thread[FooPool-FooThread].

MRE
источник

Ответы:

118

Вы могли бы поставить ThreadFactoryк newSingleThreadScheduledExecutor(ThreadFactory threadFactory). Фабрика будет отвечать за создание потоков и сможет называть их.

Процитирую Javadoc :

Создание новых тем

Новые темы создаются с использованием ThreadFactory. Если не указано иное, Executors.defaultThreadFactory()используется a , который создает все потоки ThreadGroupс одинаковым NORM_PRIORITYприоритетом и статусом, не являющимся демоном. Предоставляя другое ThreadFactory, вы можете изменить имя потока, группу потоков, приоритет, состояние демона и т. Д. Если ThreadFactoryне удается создать поток при запросе, возвращая значение null from newThread, исполнитель продолжит работу, но не сможет выполнить какие-либо задачи.

NPE
источник
283

Гуава почти всегда имеет то, что вам нужно .

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

и передай это твоему ExecutorService.

pathikrit
источник
3
Это круто!
Мартин Всетичка
25
Печально! :-(
exic
Я не уверен, где найти "гуаву". Google Guava состоит из множества частей и десятков библиотек с одинаковыми именами. Я предполагаю, что вы имеете в виду search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . Это правильно? Ссылка, которую вы предоставляете, предполагает, что она от Google, но у Google также есть около полдюжины артефактов на Maven / Sonatype с именем "guava".
Джейсон
@Jason - Если вы пишете нетривиальный Java-проект, скорее всего, у вас уже есть guava в качестве зависимости. И вот оно: github.com/google/guava
pathikrit
@pathikrit, спасибо! Я думаю, что мне нужно больше учиться на Гуаве :-)
Джейсон
95

Вы можете попытаться предоставить собственную фабрику потоков, которая будет создавать потоки с соответствующими именами. Вот один пример:

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

Executors.newSingleThreadExecutor(new YourThreadFactory()).submit(someRunnable);
Никита Белахлазау
источник
58

Вы также можете впоследствии изменить имя вашего потока, пока поток выполняется:

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

Это может быть интересно, если, например, вы используете одну и ту же ThreadFactory для разных типов задач.

FlorianT
источник
7
Это сработало, потому что, как описано в FlorianT, у меня было много разных типов потоков, и мне не хотелось создавать несколько объектов ThreadFactory только для имени. Я вызвал Thread.currentThread (). SetName ("FooName"); в первой строке каждого метода run ().
Робин Циммерманн
5
Одна небольшая проблема с этим, когда поведение отказа описан в документации происходит: (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.). Если ExecutorService заменяет поток, он будет назван ThreadFactory. Опять же, видение исчезновения имени во время отладки может быть полезным индикатором.
Sethro
Просто великолепно! Спасибо.
спрашивает
1
Как говорится в другом ответе, это быстрый и грязный метод для установки имени, и если вы сделаете это с несколькими потоками, у всех будет одинаковое имя !!
Тано
Может потребоваться вернуть имя потока в исходное состояние при выходе, поскольку оно может сохранить имя, даже если оно работает над различными несвязанными задачами.
Дастин К
51

Параметр BasicThreadFactoryfrom apache commons-lang также полезен для обеспечения поведения именования. Вместо того, чтобы писать анонимный внутренний класс, вы можете использовать Builder для именования потоков так, как вы хотите. Вот пример из 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

Если вы используете Spring, CustomizableThreadFactoryдля этого вы можете установить префикс имени потока.

Пример:

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

Кроме того, вы можете создать свой ExecutorServiceобъект как Spring bean, используя ThreadPoolExecutorFactoryBean- тогда все потоки будут названы с beanName-префиксом.

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

В приведенном выше примере потоки будут названы с myExecutor-префиксом. Вы можете явно установить префикс на другое значение (например, "myPool-"), установив executorFactoryBean.setThreadNamePrefix("myPool-")на заводском компоненте.

Адам Михалик
источник
не можете найти CustomizableThreadFactory? Я использую JDK 1,7. Есть идеи, что мне здесь не хватает?
Камран Шахид
@KamranShahid это класс Spring Framework, для его использования необходимо использовать Spring
Адам Михалик
20

Для этого есть открытый RFE с Oracle. Судя по комментариям сотрудника Oracle, они не понимают проблему и не решат ее. Это одна из тех вещей, которые очень просто поддерживать в JDK (не нарушая обратной совместимости), поэтому стыдно, что RFE неправильно поняли.

Как уже указывалось, вам нужно реализовать свой собственный ThreadFactory . Если вы не хотите использовать Guava или Apache Commons только для этой цели, я приведу здесь ThreadFactoryреализацию, которую вы можете использовать. Это в точности похоже на то, что вы получаете из JDK, за исключением возможности установить для префикса имени потока нечто иное, чем «пул».

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

Когда вы хотите использовать его, вы просто используете тот факт, что все Executorsметоды позволяют вам предоставить свои собственные ThreadFactory.

это

    Executors.newSingleThreadExecutor();

даст ExecutorService, где имена потоков, pool-N-thread-Mно с помощью

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

вы получите ExecutorService, где названы потоки primecalc-N-thread-M. Вуаля!

peterh
источник
Вы пропустили закрывающую скобку в своем последнем фрагменте
k.liakos
Просто быстрое замечание, что SonarLint / Qube предпочитает не использовать ThreadGroupв пользу ThreadPoolExecutor.
Дрейки
8
private class TaskThreadFactory implements ThreadFactory
{

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

        return t;
    }

}

Передайте ThreadFactory в службу executor, и все готово

I.Tyger
источник
8

Быстрый и грязный способ заключается в использовании Thread.currentThread().setName(myName);в run()методе.

Кирк
источник
7

Расширить ThreadFactory

public interface ThreadFactory

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

Thread newThread(Runnable r)

Создает новую тему. Реализации также могут инициализировать приоритет, имя, состояние демона, группу нитей и т. Д.

Образец кода:

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

вывод:

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

....и т.д

Равиндра Бабу
источник
1
Ваш счетчик потоков не является потокобезопасным: вам следует использовать AtomicInteger.
Пино
Спасибо за предложение. Я включил ваше предложение.
Равиндра Бабу
5

Как уже говорилось в других ответах, вы можете создать и использовать собственную реализацию java.util.concurrent.ThreadFactoryинтерфейса (внешние библиотеки не требуются). Я вставляю свой код ниже, потому что он отличается от предыдущих ответов, поскольку он использует String.formatметод и принимает базовое имя для потоков в качестве аргумента конструктора:

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

И это пример использования:

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

РЕДАКТИРОВАТЬ : сделать мою ThreadFactoryреализацию потокобезопасной, спасибо @mchernyakov за указание на это.
Несмотря на то, что нигде в ThreadFactoryдокументации не сказано, что его реализации должны быть поточно-ориентированными, тот факт, что DefaultThreadFactoryпоточнобезопасность, является большой подсказкой:

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
Ваш счетчик потоков (threadsNum) не является потокобезопасным, вы должны использовать AtomicInteger.
Мчерняков
Спасибо за указание, @mchernyakov Я только что отредактировал свой ответ соответственно.
Виктор Гил
4

Самодельное ядро ​​Java-решение, которое я использую для украшения существующих фабрик:

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

В действии:

Executors.newSingleThreadExecutor(new ThreadFactoryNameDecorator("foo"));
Гжегож Пивоварек
источник
3
Executors.newSingleThreadExecutor(r -> new Thread(r, "someName")).submit(getJob());

Runnable getJob() {
        return () -> {
            // your job
        };
}
многие мне
источник
3

Вы можете написать свою собственную реализацию ThreadFactory, используя, например, некоторую существующую реализацию (например, defaultThreadFactory) и изменить имя в конце.

Пример реализации 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;
    }
}

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

Executors.newSingleThreadExecutor(new ThreadFactoryWithCustomName(
        Executors.defaultThreadFactory(),
        "customName")
    );
К. Гол
источник
3

Я использую, чтобы сделать то же самое, как показано ниже (требуется guavaбиблиотека):

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("SO-POOL-%d").build();
ExecutorService executorService = Executors.newFixedThreadPool(5,namedThreadFactory);
Bittu
источник
1
Стоит отметить, что ThreadFactoryBuilderэто из библиотеки Google Guava.
Крейг Отис
3

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

Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Your name"));
CamW
источник
это создает две темы. Один с именем "Твое имя", а другой "пул-н-нить-М"
Systemsplanet
@ Systemplanet Нет, это не так. Извлечение дампа потока из минимального примера, в котором исполнитель запускает main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
спящий
Хм, это было, когда я попробовал это. Это имеет смысл, поскольку, если вы передадите ему новый Runnable (), он создаст для вас поток, а вы создадите поток самостоятельно.
Systemplanet
Я ожидаю, что вместо этого вы использовали ThreadPoolExecutor или он был запущен для какой-то другой цели. Этот код не будет создавать поток "pool-N-thread-M". Кроме того, я не верю, что в этом есть смысл. Ваше утверждение «если вы передадите ему новый Runnable (), оно создаст для вас поток» неверно. Он использует этот runnable для создания потока и делает это один раз, потому что он является однопоточным исполнителем. Создана только 1 тема.
CamW
2

Это моя индивидуальная фабрика, предоставляющая индивидуальные имена для анализаторов дампа потоков. Обычно я просто даю tf=nullповторно использовать фабрику потоков JVM по умолчанию. Этот сайт имеет более продвинутую фабрику ниток.

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

Для вашего удобства это цикл дампа потока для отладки.

    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()
            );
        }
    }
Whome
источник
Это сработало очень хорошо для меня, несколько удивило, что за него не проголосовали. В любом случае ура.