ExecutorService的线程和线程池如何命名?

293

假设我有一个应用程序,它像这样利用了Executor框架

Executors.newSingleThreadExecutor().submit(new Runnable(){
    @Override
    public void run(){
        // do stuff
    }
}
当我在调试器中运行此应用程序时,将创建一个线程,其名称为:Thread [pool-1-thread-1](默认名称)。正如您所见,这并不是非常有用的,并且据我所知,Executor框架没有提供一种简单的方式来为创建的线程或线程池命名。
那么,如何为线程/线程池提供名称呢?例如,Thread [FooPool-FooThread]

2
cccctcu02252458ubnuccccctcu22264745 - Sekiro
20个回答

336

Guava几乎总是拥有你需要的东西。

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

并将其传递给您的ExecutorService


4
我不确定在哪里可以找到“guava”。Google的Guava有很多部分,而且有许多同名的库。我猜你指的是https://search.maven.org/artifact/com.google.guava/guava/29.0-jre/bundle。是这样吗?你提供的链接表明它来自Google,但Google在Maven/Sonatype上也有大约六个名为“guava”的构件。 - Jason
@Jason - 如果你正在编写一个非平凡的Java项目,你很可能已经将guava作为依赖项。这里是它的链接:https://github.com/google/guava - pathikrit
@pathikrit,谢谢!我想我需要更多地学习Guava :-) - Jason
2
如果您没有使用Guava,Apache Commons Lang也有一个非常类似的BasicThreadFactory。 - Matt Y

141
您可以为newSingleThreadScheduledExecutor(ThreadFactory threadFactory)提供一个ThreadFactory。该工厂将负责创建线程,并能够对其进行命名。
引用Javadoc的话:

创建新线程

使用ThreadFactory来创建新线程。如果未明确指定,则使用 Executors.defaultThreadFactory(),它创建的所有线程都在同一ThreadGroup中,并具有相同的NORM_PRIORITY优先级和非守护进程状态。您可以通过提供不同的ThreadFactory来更改线程的名称、线程组、优先级、守护进程状态等。如果ThreadFactory无法成功创建线程并从newThread返回null时被请求,则执行者将继续执行,但可能无法执行任何任务。


123

您可以尝试提供自己的线程工厂,以创建带有适当名称的线程。以下是一个示例:

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

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

或者在 Kotlin 中

Executors.newSingleThreadExecutor { r -> Thread(r, "Your name") }

76

在执行线程的同时,您也可以随后更改线程的名称:

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

如果你正在为不同类型的任务使用相同的ThreadFactory,那么这可能会很有趣。


11
这种方法非常有用,因为正如FlorianT所描述的那样,我有很多不同类型的线程,不想为了名称而创建多个ThreadFactory对象。我在每个run()方法的第一行调用了Thread.currentThread().setName("FooName"); - Robin Zimmermann
9
其中一个小问题是文档中描述的故障行为:(请注意,如果此单个线程在关闭之前由于执行期间发生故障而终止,则如有需要,将创建一个新线程来执行后续任务。)。如果ExecutorService替换了该线程,它将由ThreadFactory命名。不过,在调试时看到名称消失可能是一个有用的指示器。 - sethro
太棒了!谢谢。 - asgs
1
正如其他答案所述,这是一种快速而简单的方法来设置名称,如果您在多个线程中这样做,所有线程都将具有相同的名称!! - Tano
3
可能需要在退出时将线程名称设置回原始名称,因为即使在执行不相关的任务时,它也可能保留名称。 - Dustin K

59

来自 Apache Commons-Lang 的 BasicThreadFactory 也可以用于提供命名行为。你可以使用 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);

53
如果您正在使用Spring,则可以使用CustomizableThreadFactory,并设置线程名称前缀。
示例:
ExecutorService alphaExecutor =
    Executors.newFixedThreadPool(10, new CustomizableThreadFactory("alpha-"));

或者,您可以使用ThreadPoolExecutorFactoryBean将您的ExecutorService创建为Spring bean - 然后所有线程都将以beanName-前缀命名。

@Bean
public ThreadPoolExecutorFactoryBean myExecutor() {
    ThreadPoolExecutorFactoryBean executorFactoryBean = new ThreadPoolExecutorFactoryBean();
    // configuration of your choice
    return executorFactoryBean;
}
在上述示例中,线程将以myExecutor-前缀命名。您可以通过在工厂bean上设置executorFactoryBean.setThreadNamePrefix("myPool-")来显式地将前缀设置为其他值(例如"myPool-")。

找不到CustomizableThreadFactory?我正在使用JDK 1.7。这里缺少什么东西,你有什么想法吗? - Kamran Shahid
1
@KamranShahid 这是一个Spring框架的类,你必须使用Spring才能使用它。 - Adam Michalik

30

在Oracle中有一个与此相关的开放式RFE。从Oracle员工的评论中可以看出,他们不理解这个问题,也不会修复它。这是JDK中非常简单的支持方式之一(不会破坏向后兼容性),所以很遗憾这个RFE被误解了。

正如所指出的那样,您需要自己实现ThreadFactory。如果您不想为此目的引入Guava或Apache Commons,我在这里提供了一个ThreadFactory实现,您可以使用它。它与您从JDK中获得的完全相似,只是可以将线程名称前缀设置为其他内容而不是“pool”。

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 更喜欢使用 ThreadPoolExecutor 而不是 ThreadGroup - Drakes
准确地说,SonarLint 符合代码规范,但如果您更改类,则无法再进行编译。 - Stefano Bossi
非常好,它保留了池编号/线程编号 :) - rogerdpack

13

正如其他答案已经提到的,您可以创建并使用自己实现的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);

编辑:感谢@mchernyakov指出,使我的ThreadFactory实现线程安全。
尽管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)));
    }    
}

5
您的线程计数器(threadsNum)不是线程安全的,您应该使用AtomicInteger。 - mchernyakov
感谢您指出,@mchernyakov。我已经相应地编辑了我的答案。 - Víctor Gil

10

如果您只想为单个线程执行器更改名称,我发现使用 Lambda 作为线程工厂最容易。

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

这将创建两个线程。一个名为“你的名字”,另一个名为“pool-N-thread-M”。 - Systemsplanet
@Systemsplanet 不,它不会。从使用执行器运行睡眠线程的最小示例中获取线程转储将显示以下线程:main@1、Finalizer@667、Reference Handler@668、Your name@665、Signal Dispatcher@666 - CamW
嗯,我尝试过它,它确实可以。这是有道理的,因为如果你传递一个新的Runnable(),它会为你创建一个线程,而你自己也在创建一个线程。 - Systemsplanet
2
我预计你使用了ThreadPoolExecutor或者有其他目的在运行。这段代码不会创建一个“pool-N-thread-M”的线程。而且,我认为它没有意义去这样做。你的说法“如果你传递一个新的Runnable(),它会为你创建一个线程”是不正确的。它使用该runnable来创建一个线程,并且只执行一次,因为它是单线程执行器。只有1个线程被创建。 - CamW

9

一种快速简单的方法是在run()方法中使用Thread.currentThread().setName(myName);


1
FlorianT的答案的副本? - rogerdpack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接