การตั้งชื่อเธรดและเธรดพูลของ ExecutorService


228

สมมติว่าฉันมีแอปพลิเคชั่นที่ใช้ Executorเฟรมเวิร์ก

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

เมื่อฉันเรียกใช้แอปพลิเคชันนี้ในดีบักเกอร์เธรดจะถูกสร้างขึ้นด้วยชื่อ (ค่าเริ่มต้น) ต่อไปนี้: Thread[pool-1-thread-1]ไปนี้: อย่างที่คุณเห็นนี่มันไม่มีประโยชน์มากนักและเท่าที่ฉันบอกได้Executorเฟรมเวิร์กไม่มีวิธีง่าย ๆ ในการตั้งชื่อเธรดหรือเธรดพูลที่สร้างขึ้น

ดังนั้นหนึ่งจะไปเกี่ยวกับการให้ชื่อสำหรับเธรด / thread-pool อย่างไร 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
มันอัศจรรย์มาก!
Martin Vseticka

25
เศร้าจัง! :-(
exic

ฉันไม่แน่ใจว่าจะหา "ฝรั่ง" ได้ที่ไหน Guava ของ 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 ()
Robin Zimmermann

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

ยอดเยี่ยมเพียง! ขอบคุณ.
asgs

1
ดังที่คำตอบอื่น ๆ บอกว่านี่เป็นวิธีที่รวดเร็วและสกปรกในการตั้งชื่อและถ้าคุณทำเช่นนั้นด้วยหลายกระทู้ทุกคนจะมีชื่อเหมือนกัน !!
Tano

อาจต้องการตั้งชื่อเธรดกลับไปเป็นต้นฉบับเมื่อออกเนื่องจากอาจเก็บชื่อไว้แม้ว่ามันจะทำงานกับงานที่ไม่เกี่ยวข้องต่างกัน
ดัสติน K

51

BasicThreadFactoryจาก Apache Commons-lang ยังเป็นประโยชน์เพื่อให้พฤติกรรมการตั้งชื่อ แทนที่จะเขียนคลาสภายในที่ไม่ระบุชื่อคุณสามารถใช้ตัวสร้างเพื่อตั้งชื่อเธรดตามที่คุณต้องการ นี่คือตัวอย่างจาก 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-")จากโรงงานถั่ว


ไม่สามารถหา CustomizableThactFactory ได้? ฉันใช้ jdk 1.7 ความคิดใดที่ฉันหายไปที่นี่
Kamran Shahid

@ KamranShahid นี่เป็นคลาส Spring Framework คุณต้องใช้ Spring เพื่อรับมัน
Adam Michalik

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

เมื่อคุณต้องการที่จะใช้มันคุณก็สามารถใช้ประโยชน์จากความจริงที่ว่าทุกวิธีการช่วยให้คุณเพื่อให้คุณเองExecutorsThreadFactory

นี้

    Executors.newSingleThreadExecutor();

จะให้ ExecutorService โดยที่ชื่อเธรดมีpool-N-thread-Mแต่ใช้

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

คุณจะได้รับ ExecutorService primecalc-N-thread-Mที่หัวข้อที่มีการตั้งชื่อ Voila!


คุณพลาดวงเล็บปิดในตัวอย่างสุดท้ายของคุณ
k.liakos

เพียงอย่างรวดเร็วทราบว่า SonarLint / Qube ชอบที่จะได้ใช้ในความโปรดปรานของThreadGroup ThreadPoolExecutor
Drakes

8
private class TaskThreadFactory implements ThreadFactory
{

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

        return t;
    }

}

ส่งต่อ ThreadFactory ไปยังผู้ให้บริการและคุณก็พร้อมที่จะไป



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
Pino

ขอบคุณสำหรับคำแนะนำ ฉันได้รวมข้อเสนอแนะของคุณ
Ravindra babu

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) ไม่ใช่ threadsafe คุณควรใช้ AtomicInteger
mchernyakov

ขอบคุณที่ชี้ให้เห็น @mchernyakov ฉันเพิ่งแก้ไขคำตอบของฉันตามนั้น
Víctor Gil

4

โซลูชัน Java core ที่ปลูกเองที่ฉันใช้เพื่อตกแต่งโรงงานที่มีอยู่:

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

คุณสามารถเขียนการใช้งาน 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
Craig Otis

3

ฉันพบว่ามันง่ายที่สุดในการใช้แลมบ์ดาเป็นโรงงานด้ายถ้าคุณต้องการเปลี่ยนชื่อผู้ปฏิบัติการเธรดเดี่ยว

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

ฉันคาดหวังว่าคุณจะใช้ 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()
            );
        }
    }

มันใช้งานได้ดีจริง ๆ สำหรับฉันค่อนข้างแปลกใจว่ามันไม่ได้อัปเดตมากนัก ไชโย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.