Java中的ThreadFactory用法

jag*_*mot 52 java concurrency multithreading

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

谢谢!

Bor*_*vić 58

这是一种可能的用法.如果你有一个执行器服务,它以多线程的方式执行你的可运行任务,偶尔你的线程会从未捕获的异常中死掉.我们假设您不想记录所有这些异常.ThreadFactory解决了这个问题:

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

executor.submit(new Runnable() {
   @Override
   public void run() {
      someObject.someMethodThatThrowsRuntimeException();
   }
});
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 多年后请原谅我的评论,但这个例子有误导性.通过ExecutorService.submit()提交的Runnable任务抛出的异常将不会被UncaughtExceptionHandler捕获,只会处理ExecutorService.execute() (2认同)

And*_*s_D 43

工厂模式是用于软件开发的创建设计模式,用于封装对象创建中涉及的过程.

让我们假设我们有一些工作线程用于不同的任务,并希望它们具有特殊的名称(比如用于调试目的).所以我们可以实现一个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++);
   }
}
Run Code Online (Sandbox Code Playgroud)

如果您有这样的要求,那么在没有工厂或构建器模式的情况下实现它将非常困难.


ThreadFactory是Java API的一部分,因为它也被其他类使用.所以上面的例子说明了为什么我们应该在某些场合使用'工厂来创建线程',但是,当然,完全没有必要实现java.util.concurrent.ThreadFactory来完成这个任务.

  • @bestsss - `ThreadFactory`是一个接口,它使用*nothing*.这个简单的例子是关闭由类自己的文档提供的示例,并将重点放在工厂模式上.随意在您自己的答案中提供一个*好的例子*. (3认同)
  • 如果两个线程同时调用 newThread,@ Marcus volatile 不保证原子性。计数器必须声明为 AtomicInteger。 (2认同)

bes*_*sss 19

一些内部运作

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

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

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


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

下面是一些(希望)可重用的ThreadFactory实现代码,它可以在托管环境中使用,以确保正确ThreadGroup(可以限制优先级或中断线程)ContextClassLoader,stacksize等设置(和/或可以配置)而不泄漏.如果有任何兴趣,我可以展示如何处理w/inherited 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一些非常简单的用法:

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


Dro*_*ona 5

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

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

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