Іменування ниток та пулів потоків ExecutorService


228

Скажімо, у мене є програма, яка використовує Executor рамки як такі

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

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

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

Відповіді:


118

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

Щоб цитувати Javadoc :

Створення нових тем

Нові потоки створюються за допомогою ThreadFactory. Якщо не вказано інше, Executors.defaultThreadFactory()використовується a , який створює потоки, щоб усі були в одному ThreadGroupі тому ж NORM_PRIORITYпріоритеті та недемонтовому статусі. Надавши інше ThreadFactory, ви можете змінити ім’я потоку, групу ниток, пріоритет, статус демона тощо. Якщо ThreadFactoryне вдалося створити потік при запиті, повернувши null з newThread, виконавець продовжить, але він не зможе виконати жодних завдань


283

Гуава майже завжди має те, що потрібно .

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

і передайте його своєму ExecutorService.


3
Це фантастично!
Мартін Всетічка

25
Це сумно! :-(
exic

Я не впевнений, де знайти "гуаву". У Гуави Google дуже багато деталей і є десятки бібліотек з такою ж назвою. Я припускаю, що ви маєте на увазі search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . Це так? Посилання, яке ви надаєте, припускає, що це від Google, але Google також має близько півдесятка артефактів на Maven / Sonatype під назвою "guava".
Джейсон

@Jason - Якщо ви пишете нетривіальний проект Java, ви, швидше за все, вже повинні мати гуаву як залежність. І ось це: 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 для різних типів завдань.


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

Користувач BasicThreadFactoryвід 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
Adam Michalik,

20

Для цього у Oracle відкрита RFE . З коментарів співробітника 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. Вуаля!


Ви пропустили круглі дужки в своєму останньому фрагменті
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 виконавцеві служби, і ви готові йти


8

Швидкий і брудний спосіб - це використовувати Thread.currentThread().setName(myName);в run()методі.


7

Розширити ThreadFactory

public interface ThreadFactory

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

Thread newThread(Runnable r)

Побудова нової теми. Впровадження може також ініціалізувати пріоритет, ім'я, статус демона, ThreadGroup тощо.

Приклад коду:

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

EDIT : зробила мою 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
Ви лічильник потоків (threadNum) не є безпечним для потоків, вам слід використовувати AtomicInteger.
mchernyakov

Дякую, що вказали на це, @mchernyakov, я щойно відредагував свою відповідь.
Víctor Gil

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

1
Варто зазначити, що ThreadFactoryBuilderце з бібліотеки Google Guava.
Крейг Отіс

3

Мені найпростіше використовувати лямбда як фабрику ниток, якщо ви просто хочете змінити ім'я виконавця одного потоку.

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

це створює дві нитки. Один назвав "Ваше ім'я", а інший "пул-N-потік-М"
Systemplanet

@Systemsplanet Ні, це не так. Якщо взяти дамп потоку з мінімального прикладу, який використовує виконавця для запуску потоку, який спить, відображаються такі теми:main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
CamW

Гум, це було, коли я спробував це. Має сенс, що так, якщо ви передасте йому новий Runnable (), він створює для вас нитку, а ви створюєте нитку самостійно.
Systemplanet

Я очікую, що ви використовували ThreadPoolExecutor замість цього або працювали з якоюсь іншою метою. Цей код не створить потік "pool-N-thread-M". Крім того, я не вірю, що це має сенс, що це було б. Ваше твердження, "якщо ви передасте йому новий 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()
            );
        }
    }

Це спрацювало дуже добре для мене, дещо здивоване, це не було надзвичайно багато. У будь-якому випадку ура.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.