Nazewnictwo wątków i pul wątków w ExecutorService


228

Załóżmy, że mam aplikację, która korzysta z Executorframeworka jako takiego

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

Kiedy uruchomić tę aplikację w debugger, wątek jest tworzony z poniższej (domyślnie) imię: Thread[pool-1-thread-1]. Jak widać, nie jest to szczególnie przydatne i, o ile mogę stwierdzić, Executorframework nie zapewnia łatwego sposobu nazwania utworzonych wątków lub pul wątków.

Jak zatem można podać nazwy wątków / pul wątków? Na przykład Thread[FooPool-FooThread].

Odpowiedzi:


118

Możesz podać ThreadFactorydo newSingleThreadScheduledExecutor(ThreadFactory threadFactory). Fabryka będzie odpowiedzialna za tworzenie wątków i będzie mogła je nazwać.

Cytując Javadoc :

Tworzenie nowych wątków

Nowe wątki są tworzone za pomocą ThreadFactory. Jeśli nie określono inaczej, Executors.defaultThreadFactory()używana jest a , która tworzy wątki, aby wszystkie miały ten sam ThreadGroupi miały ten sam NORM_PRIORITYpriorytet i ten sam status demona. Podając inny ThreadFactory, możesz zmienić nazwę wątku, grupę wątków, priorytet, status demona itp. Jeśli ThreadFactoryutworzenie wątku nie powiedzie się, gdy zostaniesz o to poproszony poprzez zwrócenie wartości null od newThread, executor będzie kontynuował, ale może nie być w stanie wykonać żadnych zadań


283

Guawa prawie zawsze ma to, czego potrzebujesz .

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

i przekaż to swojemu ExecutorService.


3
To fantastycznie!
Martin Vseticka

25
To smutne! :-(
exic

Nie jestem pewien, gdzie znaleźć „guava”. Google Guava składa się z wielu części i istnieje wiele bibliotek o tej samej nazwie. Zakładam, że masz na myśli search.maven.org/artifact/com.google.guava/guava/29.0-jre/… . Czy to prawda? Podany link sugeruje, że pochodzi on od Google, ale Google ma również około pół tuzina artefaktów na Maven / Sonatype o nazwie „guava”.
Jason

@Jason - Jeśli piszesz nietrywialny projekt Java, najprawdopodobniej powinieneś już mieć guava jako zależność. A oto: github.com/google/guava
pathikrit

@pathikrit, dzięki! Myślę, że muszę uczyć się więcej na Guava :-)
Jason

95

Możesz spróbować stworzyć własną fabrykę wątków, która utworzy wątek o odpowiednich nazwach. Oto jeden przykład:

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

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

58

Możesz także później zmienić nazwę swojego wątku, gdy wątek jest wykonywany:

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

Może to być interesujące, jeśli na przykład używasz tego samego ThreadFactory do różnego rodzaju zadań.


7
Działa to ładnie, ponieważ jak opisał FlorianT, mam wiele różnych typów wątków i nie chciałem tworzyć wielu obiektów ThreadFactory tylko dla tej nazwy. Zadzwoniłem do Thread.currentThread (). SetName („FooName”); jako pierwszy wiersz w każdej metodzie run ().
Robin Zimmermann

5
Jeden drobny problem z tym jest, gdy wystąpi awaria zachowanie opisane w docs: (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.). Jeśli ExecutorService zastępuje wątek, zostanie on nazwany przez ThreadFactory. Z drugiej strony zobaczenie, jak nazwa znika podczas debugowania, może być użytecznym wskaźnikiem.
sethro

Po prostu super! Dziękuję Ci.
uważa

1
Jak mówi druga odpowiedź, jest to szybka i brudna metoda ustawiania nazwy, a jeśli zrobisz to z wieloma wątkami, wszystkie będą miały tę samą nazwę !!
Tano

Być może zechcesz przywrócić nazwę wątku z powrotem do oryginału po wyjściu, ponieważ może zachować nazwę, nawet jeśli działa na różnych niepowiązanych zadaniach.
Dustin K

51

Z BasicThreadFactoryapache commons-lang jest także przydatny do zapewnienia zachowania nazewnictwa. Zamiast pisać anonimową klasę wewnętrzną, możesz użyć Konstruktora, aby nazwać wątki według własnego uznania. Oto przykład z 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

Jeśli używasz Spring, CustomizableThreadFactorymożesz ustawić prefiks nazwy wątku.

Przykład:

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

Alternatywnie możesz utworzyć ExecutorServicefasolę wiosenną za pomocą ThreadPoolExecutorFactoryBean- wtedy wszystkie wątki zostaną nazwane z beanName-prefiksem.

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

W powyższym przykładzie wątki zostaną nazwane myExecutor-przedrostkiem. Możesz jawnie ustawić prefiks na inną wartość (np. "myPool-"), Ustawiając executorFactoryBean.setThreadNamePrefix("myPool-")na fasolę fabryczną.


nie możesz znaleźć CustomizableThreadFactory? używam jdk 1.7. jakiś pomysł, czego mi brakuje?
Kamran Shahid

@KamranShahid to klasa Spring Framework, musisz mieć Spring, aby ją mieć
Adam Michalik

20

Jest do tego otwarty RFE z Oracle. Z komentarzy pracownika Oracle wynika, że ​​nie rozumieją problemu i nie mogą go naprawić. Jest to jedna z tych rzeczy, które są bardzo proste w obsłudze w JDK (bez naruszania kompatybilności wstecznej), więc wstydem jest to, że RFE zostało źle zrozumiane.

Jak wskazano, musisz wdrożyć własny ThreadFactory . Jeśli nie chcesz pobierać Guava lub Apache Commons tylko w tym celu, podaję ThreadFactoryimplementację, z której możesz skorzystać. Jest dokładnie podobny do tego, co otrzymujesz z JDK, z wyjątkiem możliwości ustawienia prefiksu nazwy wątku na coś innego niż „pula”.

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

Kiedy chcesz z niego skorzystać, po prostu skorzystaj z faktu, że wszystkie Executorsmetody pozwalają na zapewnienie własnych ThreadFactory.

To

    Executors.newSingleThreadExecutor();

da ExecutorService, gdzie wątki są nazywane, pool-N-thread-Male przy użyciu

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

dostaniesz ExecutorService, gdzie są nazywane wątki primecalc-N-thread-M. Voila!


W ostatnim fragmencie
pominąłeś

Krótka uwaga, że ​​SonarLint / Qube woli nie używać ThreadGroupna rzecz ThreadPoolExecutor.
Drakes,

8
private class TaskThreadFactory implements ThreadFactory
{

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

        return t;
    }

}

Przekaż ThreadFactory do executorservice i możesz zacząć


8

Szybkim i brudnym sposobem jest użycie Thread.currentThread().setName(myName);tej run()metody.


7

Rozszerz ThreadFactory

public interface ThreadFactory

Obiekt, który tworzy nowe wątki na żądanie. Korzystanie z fabryk wątków eliminuje konieczność wykonywania połączeń do nowego wątku, umożliwiając aplikacjom korzystanie ze specjalnych podklas wątków, priorytetów itp.

Thread newThread(Runnable r)

Tworzy nowy wątek. Implementacje mogą również inicjować priorytet, nazwę, status demona, ThreadGroup itp.

Przykładowy kod:

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

wynik:

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

....itp


1
Licznik wątków nie jest bezpieczny dla wątków: powinieneś użyć AtomicInteger.
Pino,

Dziękuję za sugestie. Uwzględniłem twoją sugestię.
Ravindra babu

5

Jak już powiedziano w innych odpowiedziach, możesz utworzyć własną implementację java.util.concurrent.ThreadFactoryinterfejsu i korzystać z niej (nie są wymagane biblioteki zewnętrzne). Wklejam poniższy kod, ponieważ różni się on od poprzednich odpowiedzi, ponieważ używa String.formatmetody i przyjmuje podstawową nazwę wątków jako argument konstruktora:

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

A to jest przykład użycia:

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

EDYCJA : dzięki czemu moja ThreadFactoryimplementacja jest bezpieczna dla wątków, dzięki @mchernyakov za wskazanie tego.
Chociaż nigdzie w ThreadFactorydokumentacji nie jest powiedziane, że jego implementacje muszą być bezpieczne dla wątków, fakt, że DefaultThreadFactoryjest bezpieczny dla wątków, stanowi dużą wskazówkę:

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
Twój licznik wątków (ThreadNum) nie jest bezpieczny dla wątków, powinieneś użyć AtomicInteger.
mchernyakov

Dziękuję za zwrócenie uwagi, @mchernyakov Właśnie odpowiednio zredagowałem swoją odpowiedź.
Víctor Gil

4

Domowe podstawowe rozwiązanie Java, którego używam do ozdabiania istniejących fabryk:

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

W akcji:

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

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

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

3

Możesz napisać własną implementację ThreadFactory, używając na przykład niektórych istniejących implementacji (takich jak defaultThreadFactory) i zmienić nazwę na końcu.

Przykład implementacji 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;
    }
}

I użycie:

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

3

Używam tego samego, co poniżej (wymaga guavabiblioteki):

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

1
Warto zauważyć, że ThreadFactoryBuilderpochodzi z biblioteki Google Guava.
Craig Otis,

3

Uważam, że najłatwiej jest użyć lambda jako fabryki wątków, jeśli chcesz tylko zmienić nazwę dla jednego modułu wykonującego wątek.

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

to tworzy dwa wątki. Jeden o nazwie „Twoje imię” i drugi „pula-N-wątek-M”
Systemsplanet

@ Systemsplanet Nie, nie ma. Zrzut zrzutu wątku z minimalnego przykładu, który używa executora do uruchomienia wątku, który śpi, pokazuje następujące wątki:main@1, Finalizer@667, Reference Handler@668, Your name@665, Signal Dispatcher@666
CamW

Hum, zrobiłem to, kiedy spróbowałem. Ma to sens, ponieważ jeśli przekażesz mu nową funkcję Runnable (), utworzy ona dla ciebie wątek, a Ty sam utworzysz wątek.
Systemsplanet,

Oczekuję, że zamiast tego użyłeś ThreadPoolExecutor lub uruchomiłeś go w innym celu. Ten kod nie utworzy wątku „pool-N-thread-M”. Nie sądzę też, żeby miało to sens. Twoje stwierdzenie „jeśli przekażesz mu nową funkcję Runnable (), utworzy ona dla ciebie wątek” jest nieprawidłowe. Używa tej możliwości do utworzenia wątku i robi to raz, ponieważ jest to jedno-wątkowy moduł wykonujący. Utworzono tylko 1 wątek.
CamW

2

To moja spersonalizowana fabryka zapewniająca niestandardowe nazwy analizatorów zrzutów wątków. Zwykle po prostu daję tf=nullponownie użyć domyślnej fabryki wątków JVM. Ta strona ma bardziej zaawansowaną fabrykę wątków.

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

Dla Twojej wygody jest to pętla zrzutu wątku do celów debugowania.

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

Działa to dla mnie naprawdę dobrze, nieco zdziwione, że nie zostało zbyt dobrze ocenione. Tak czy inaczej, na zdrowie.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.