【问题标题】:ThreadFactory usage in JavaJava中的ThreadFactory使用
【发布时间】:2011-03-11 22:04:06
【问题描述】:

有人可以简要解释一下如何以及何时使用 ThreadFactory 吗?使用和不使用 ThreadFactory 的示例可能对理解差异非常有帮助。

谢谢!

【问题讨论】:

标签: java multithreading concurrency


【解决方案1】:

这是一种可能的用法:

假设您有一个ExecutorService,它以多线程方式执行您的Runnable 任务,并且偶尔有一个线程死于未捕获的异常。我们还假设您要记录所有这些异常。 ThreadFactory 通过允许您为线程正在执行的 Runnable 中的未捕获异常定义统一记录器来解决此问题:

ExecutorService executor = Executors.newSingleThreadExecutor(new LoggingThreadFactory());

executor.submit(new Runnable() {
   @Override
   public void run() {
      someObject.someMethodThatThrowsRuntimeException();
   }
});

LoggingThreadFactory可以这样实现:

public class LoggingThreadFactory implements ThreadFactory
{

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

        t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
        {
            @Override
            public void uncaughtException(Thread t, Throwable e)
            {
                LoggerFactory.getLogger(t.getName()).error(e.getMessage(), e);
            }
        });

        return t;
    }
}

ThreadFactory 接口是一个灵活的接口,它允许程序员处理如上所示的未捕获异常,但也允许更多地控制Thread 的创建细节(如为线程名称定义模式)它对于调试目的和生产环境都非常有用。

【讨论】:

  • 请原谅我多年后的评论,但这个例子具有误导性。通过ExecutorService.submit()提交的Runnable任务抛出的异常不会被UncaughtExceptionHandler捕获,只会处理ExecutorService.execute()
  • 嗨,我应该为可调用线程使用什么,我找不到任何东西,感谢您的帮助。
【解决方案2】:

工厂模式是一种创建型设计模式,用于软件开发,用于封装对象创建所涉及的过程。

假设我们有一些用于不同任务的工作线程,并希望它们具有特殊名称(例如用于调试目的)。所以我们可以实现一个 ThreadFactory:

public class WorkerThreadFactory implements ThreadFactory {
   private int counter = 0;
   private String prefix = "";

   public WorkerThreadFactory(String prefix) {
     this.prefix = prefix;
   }

   public Thread newThread(Runnable r) {
     return new Thread(r, prefix + "-" + counter++);
   }
}

如果您有这样的需求,那么如果没有工厂模式或构建器模式,将很难实现它。


ThreadFactory 是 Java API 的一部分,因为它也被其他类使用。所以上面的例子说明了为什么我们应该在某些情况下使用'a factory to create Threads',但是当然,绝对不需要实现java.util.concurrent.ThreadFactory来完成这个任务。

【讨论】:

  • 这是一个非常糟糕的例子,因为 ThreadFactory 使用调用者的 ThreadGroup、ContextClassLoader 和 AccessControlContext(newThread(Runnable) 通常是产生结果并将它们放入队列的线程)。至少 ThreadGroup 应保留在 c-tor 中作为参考,并且应使用创建线程。
  • @bestsss - ThreadFactory 是一个接口,它使用 nothing。这个简单的例子与类自己的文档提供的例子相近,重点放在了工厂模式上。随意在您自己的答案中提供一个好例子
  • 该评论无意冒犯,我很清楚 ThreadFactory 文档中的内容。 Executors.defaultThreadFactory() 和 Executors.privilegedThreadFactory() 是很好的起点[尤其是后者](尽管它们缺乏任何合理的命名),Executors.privilegedThreadFactory() 的文档也相当不错。
  • 计数器不需要是原子的或至少是易失的,否则如果同时创建多个线程,它们可能具有相同的计数器。
  • @ Marcus volatile 如果两个线程同时调用 newThread,则不保证原子性。计数器必须声明为 AtomicInteger。
【解决方案3】:

一些内部运作

除了一些不太容易看到的内部作品外,该主题涵盖得很好。 在使用构造函数创建线程时,新创建的线程会继承当前线程:

  • ThreadGroup(除非提供或System.getSecurityManager().getThreadGroup() 返回任意ThreadGroup) - 在某些情况下,线程组本身可能很重要,并且可能导致线程不正确终止/中断。 ThreadGroup 将作为默认异常处理程序。
  • ContextClassLoader - 在托管环境中这不应该是一个大问题,因为环境应该切换 CCL,但如果你要实现它 - 请记住。泄露调用者的 CCL 非常糟糕,线程组也是如此(尤其是如果 threadGroup 是某个子类而不是直接java.lang.ThreadGroup - 需要覆盖ThreadGroup.uncaughtException
  • AccessControlContext - 在这里,几乎没有什么可做的(除了在专用线程中开始),因为该字段仅供内部使用,很少有人怀疑它的存在。
  • 堆栈大小(通常未指定,但根据调用者的不同,获得堆栈大小非常窄的线程可能很有趣)
  • 优先级 - 大多数人都知道并倾向于设置它(或多或少)
  • 守护进程状态 - 通常这不是很重要且容易引起注意(如果应用程序刚刚消失)
  • 最后:线程继承调用者的InheritableThreadLocal - 这可能(或可能不会)导致一些影响。除了将线程生成到专用线程之外,什么也做不了。

根据应用程序,上述几点可能完全没有影响,但在某些情况下,其中一些可能会导致难以检测到的类/资源泄漏并且表现出不确定的行为。


那会写一个超长的帖子,但是……

下面是ThreadFactory实现的一些(希望)可重用代码,它可以在托管环境中使用以确保正确的ThreadGroup(可以限制优先级或中断线程),ContextClassLoader , stacksize 等已设置(和/或可以配置)并且不会泄漏。如果有任何兴趣,我可以展示如何处理继承的ThreadLocals 或继承的 acc(这基本上可以泄漏调用 classloader

package bestsss.util;

import java.lang.Thread.UncaughtExceptionHandler;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadFactoryX implements ThreadFactory{
    //thread properties
    long stackSize;
    String pattern;
    ClassLoader ccl;
    ThreadGroup group;
    int priority;
    UncaughtExceptionHandler exceptionHandler;
    boolean daemon;

    private boolean configured;

    private boolean wrapRunnable;//if acc is present wrap or keep it
    protected final AccessControlContext acc;

    //thread creation counter
    protected final AtomicLong counter = new AtomicLong();

    public ThreadFactoryX(){        
        final Thread t = Thread.currentThread();
        ClassLoader loader;
    AccessControlContext acc = null;
    try{
        loader =  t.getContextClassLoader();
        if (System.getSecurityManager()!=null){
            acc = AccessController.getContext();//keep current permissions             
            acc.checkPermission(new RuntimePermission("setContextClassLoader"));
        }
    }catch(SecurityException _skip){
        //no permission
        loader =null;
        acc = null;
    }

    this.ccl = loader;
    this.acc = acc;
    this.priority = t.getPriority();    
    this.daemon = true;//Executors have it false by default

    this.wrapRunnable = true;//by default wrap if acc is present (+SecurityManager)

    //default pattern - caller className
    StackTraceElement[] stack =  new Exception().getStackTrace();    
    pattern(stack.length>1?getOuterClassName(stack[1].getClassName()):"ThreadFactoryX", true);     
    }

    public ThreadFactory finishConfig(){
        configured = true;
        counter.addAndGet(0);//write fence "w/o" volatile
        return this;
    }

    public long getCreatedThreadsCount(){
        return counter.get();
    }

    protected void assertConfigurable(){
        if (configured)
            throw new IllegalStateException("already configured");
    }

    private static String getOuterClassName(String className){
        int idx = className.lastIndexOf('.')+1;
        className = className.substring(idx);//remove package
        idx = className.indexOf('$');
        if (idx<=0){
            return className;//handle classes starting w/ $
        }       
        return className.substring(0,idx);//assume inner class

    }

    @Override
    public Thread newThread(Runnable r) {
        configured = true;
        final Thread t = new Thread(group, wrapRunnable(r), composeName(r), stackSize);
        t.setPriority(priority);
        t.setDaemon(daemon);
        t.setUncaughtExceptionHandler(exceptionHandler);//securityException only if in the main group, shall be safe here
        //funny moment Thread.getUncaughtExceptionHandler() has a race.. badz (can throw NPE)

        applyCCL(t);
        return t;
    }

    private void applyCCL(final Thread t) {
        if (ccl!=null){//use factory creator ACC for setContextClassLoader
            AccessController.doPrivileged(new PrivilegedAction<Object>(){
                @Override
                public Object run() {
                    t.setContextClassLoader(ccl);
                    return null;
                }                               
            }, acc);        
        }
    }
    private Runnable wrapRunnable(final Runnable r){
        if (acc==null || !wrapRunnable){
            return r;
        }
        Runnable result = new Runnable(){
            public void run(){
                AccessController.doPrivileged(new PrivilegedAction<Object>(){
                    @Override
                    public Object run() {
                        r.run();
                        return null;
                    }                               
                }, acc);
            }
        };
        return result;      
    }


    protected String composeName(Runnable r) {
        return String.format(pattern, counter.incrementAndGet(), System.currentTimeMillis());
    }   


    //standard setters allowing chaining, feel free to add normal setXXX    
    public ThreadFactoryX pattern(String patten, boolean appendFormat){
        assertConfigurable();
        if (appendFormat){
            patten+=": %d @ %tF %<tT";//counter + creation time
        }
        this.pattern = patten;
        return this;
    }


    public ThreadFactoryX daemon(boolean daemon){
        assertConfigurable();
        this.daemon = daemon;
        return this;
    }

    public ThreadFactoryX priority(int priority){
        assertConfigurable();
        if (priority<Thread.MIN_PRIORITY || priority>Thread.MAX_PRIORITY){//check before actual creation
            throw new IllegalArgumentException("priority: "+priority);
        }
        this.priority = priority;
        return this;
    }

    public ThreadFactoryX stackSize(long stackSize){
        assertConfigurable();
        this.stackSize = stackSize;
        return this;
    }


    public ThreadFactoryX threadGroup(ThreadGroup group){
        assertConfigurable();
        this.group= group;
        return this;        
    }

    public ThreadFactoryX exceptionHandler(UncaughtExceptionHandler exceptionHandler){
        assertConfigurable();
        this.exceptionHandler= exceptionHandler;
        return this;                
    }

    public ThreadFactoryX wrapRunnable(boolean wrapRunnable){
        assertConfigurable();
        this.wrapRunnable= wrapRunnable;
        return this;                        
    }

    public ThreadFactoryX ccl(ClassLoader ccl){
        assertConfigurable();
        this.ccl = ccl;
        return this;
    }
}

还有一些很简单的用法:

ThreadFactory factory = new TreadFactoryX().priority(3).stackSize(0).wrapRunnable(false).pattern("Socket workers", true).
daemon(false).finishConfig();

【讨论】:

  • 这似乎类似于 Google Guava 的 ThreadFactoryBuilder。尽管我不相信他们会像您那样处理 ClassLoader。见guava-libraries.googlecode.com/svn/tags/release05/javadoc/com/…
  • @Darwyn,我对Guava一无所知(在某种程度上我没有使用它,也没有研究过它的代码),上面的代码完全来自个人经验。实际上类加载器处理对任何中间件都是有害的,我什至不能强调。
  • 嗨,我应该为可调用线程使用什么,我找不到任何东西,感谢您的帮助。
【解决方案4】:

恕我直言,ThreadFactory 最重要的一个功能是为线程命名一些有用的东西。在一个名为 pool-1-thread-2 或更糟的 Thread-12 的堆栈跟踪中拥有线程在诊断问题时是一件非常痛苦的事情。

当然,拥有ThreadGroup,守护进程状态和优先级也都很有用。

【讨论】:

    【解决方案5】:

    使用自定义线程工厂始终是一个好习惯。默认工厂用处不大。您应该使用自定义工厂,原因如下:

    1. 拥有自定义线程名称
    2. 在线程类型之间进行选择
    3. 选择线程优先级
    4. 处理未捕获的异常

    查看这篇文章: http://wilddiary.com/understanding-java-threadfactory-creating-custom-thread-factories/

    【讨论】:

      【解决方案6】:

      正如“InsertNickHere”所述,您必须了解Factory Pattern

      使用 ThreadFactory 的一个很好的例子是 ThreadPoolExecutor: Executor 将在必要时创建线程并负责池化。如果您想介入创建过程并为创建的线程赋予特殊名称,或者将它们分配给 ThreadGroup,您可以为此创建一个 ThreadFactory 并将其提供给 Executor。

      有点IoC-style。

      【讨论】:

        【解决方案7】:

        Java中ThreadFactory的使用

        按需创建新线程的对象。使用线程工厂消除了对 new Thread 的调用的硬连线,使应用程序能够使用特殊的线程子类、优先级等。

        这个接口最简单的实现就是:

        class SimpleThreadFactory implements ThreadFactory {
           public Thread newThread(Runnable r) {
             return new Thread(r);
           }
         }
        

        ThreadPoolExecutor.java 的DefaultThreadFactory

        static class DefaultThreadFactory implements ThreadFactory {
                private static final AtomicInteger poolNumber = new AtomicInteger(1);
                private final ThreadGroup group;
                private final AtomicInteger threadNumber = new AtomicInteger(1);
                private final String namePrefix;
        
                DefaultThreadFactory() {
                    SecurityManager s = System.getSecurityManager();
                    group = (s != null) ? s.getThreadGroup() :
                                          Thread.currentThread().getThreadGroup();
                    namePrefix = "pool-" +
                                  poolNumber.getAndIncrement() +
                                 "-thread-";
                }
        
                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;
                }
            }
        

        Source

        【讨论】:

          【解决方案8】:

          ThreadFactory 是一个具有单一方法的接口 public abstract java.lang.Thread newThread(java.lang.Runnable arg0);

          它的使用取决于您的要求。假设您希望某个特定功能始终创建守护线程。您可以使用 ThreadFactory 轻松实现此目的。

          以下代码仅用于说明基本情况。它没有执行任何特定功能。

          package TestClasses;
          import java.util.concurrent.ThreadFactory;
          public class ThreadFactoryEx implements ThreadFactory{
              @Override
              public Thread newThread(Runnable r) {
                  Thread t = new Thread(r);
                  t.setDaemon(true);
                  return t;
              }
          }
          
          package TestClasses;
          import java.util.concurrent.ThreadPoolExecutor;
          public class RunnableEx implements Runnable{
              @Override
              public void run() {
                  // TODO Auto-generated method stub
                  for (int i = 0; i < 5; i++) {
                      System.out.println("in a loop" + i + "times");
                  }
              }
          }
          
          
          package TestClasses;
          
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          
          public class Thread1 {
              public static void main(String[] args) {
                  ExecutorService exe = Executors.newCachedThreadPool(new ThreadFactoryEx());
                  for (int i = 0; i < 4; i++) {
                      exe.execute(new RunnableEx());
                  }
              }
          }
          

          【讨论】:

            【解决方案9】:

            ThreadFactory 会有用的

            • 用于设置更具描述性的线程名称
            • 设置线程守护进程状态
            • 用于设置线程优先级

            您可以使用来自 google Guava lib 的 ThreadFactoryBuilder 像这样创建 ThreadFactory

            ThreadFactory threadFactory = new ThreadFactoryBuilder()
                    .setNameFormat("MyThreadPool-Worker-%d")
                    .setDaemon(true)
                    .build();
            

            【讨论】:

              【解决方案10】:

              看看VerboseThreads(实现ThreadFactory)来自jcabi-log。此实现使Threads 在异常被抛出时记录异常。当您需要查看线程何时以及为何死亡时非常有用。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2010-10-22
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-04-10
                相关资源
                最近更新 更多