ThreadFactory usage in Java

前端 未结 10 1910
旧巷少年郎
旧巷少年郎 2020-12-02 05:08

Can someone briefly explain on HOW and WHEN to use a ThreadFactory? An example with and without using ThreadFactory might be really helpful to understand the differences.

10条回答
  •  我在风中等你
    2020-12-02 05:42

    Some inner workings

    The topic is covered quite well except for some inner works that are not easily visible. While creating a thread w/ the constructor the newly created thread inherits current threads:

    • ThreadGroup (unless supplied or System.getSecurityManager().getThreadGroup() returns arbitrary ThreadGroup) - The thread group on its right own can be important in some cases and can result in improper thread termination/interruption. The ThreadGroup will stand as default exception handler.
    • ContextClassLoader - in managed environment that should not be a great issue since the environment shall switch the CCL, but if you are to implement that - keep it mind. Leaking the caller's CCL is quite bad, so is the thread group (esp. if the threadGroup is some subclass and not direct java.lang.ThreadGroup - need to override ThreadGroup.uncaughtException)
    • AccessControlContext - here, there is virtually nothing to be done (except starting in a dedicated thread) since the field is for internal usage only, and few even suspect the existence of.
    • stack size (usually it's unspecified but it can a -fun- thing to get a thread w/ very narrow stack size, based on the caller)
    • priority - most people do know about and tend to set it (more or less)
    • daemon status - usually that's not very important and easily noticable (if the application just disappears)
    • Lastly: the thread inherits caller's InheritableThreadLocal - which may (or may not) lead to some implications. Again nothing can be done about, besides spawning the thread into a dedicated thread.

    Depending on the application the points above may have no effect at all but in some cases, some of them can lead to Class/Resources leaks that are hard to detect and exhibit not deterministic behavior.


    That would make an extra long post but so...

    below is some (hopefully) reusable code for ThreadFactory implementation, it can be used in managed environments to ensure proper ThreadGroup (which can limit priority or interrupt threads), ContextClassLoader, stacksize and so on are set (and/or can be configured) and not leaked. If there is any interest I can show how to deal w/ inherited ThreadLocals or the inherited acc (which essentially can leak the calling 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(){
                    @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(){
                        @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 %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;
        }
    }
    
    
    

    Also some very simple usage:

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

    提交回复
    热议问题