【问题标题】:Append stacktrace of caller thread into the new thread being created, for easier debugging将调用者线程的堆栈跟踪附加到正在创建的新线程中,以便于调试
【发布时间】:2014-07-11 06:10:50
【问题描述】:

我在调试时经常遇到问题。

有时线程结束时会抛出异常。

这个问题的原因是线程的调用者/启动者。

调用者发送了错误的参数或调用了线程而没有初始化。

为了找到调用特定线程的位置,需要一些额外的努力, 因为堆栈跟踪是无用的。

如果我们可以将调用者线程的堆栈跟踪附加到被调用线程中。

考虑以下示例:

public class ThreadTest {
        Thread t = new Thread("executeNonBlocking") {
            @Override public void run() {
                // What would be a good way to 
                // append callerStackTrace to the stack
                // trace of this thread
                System.out.println("inside");
                new Throwable().printStackTrace();
            }
        };

    public void executeNonBlocking() {
        final StackTraceElement[] callerStackTrace = new Throwable().getStackTrace();
        new Throwable().printStackTrace();
        t.start();
    }

    public static void main(String[] args) {
        new ThreadTest().executeNonBlocking();
    }
}

输出

java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
        at ThreadTest$1.run(ThreadTest.java:34)

期望的输出

java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
        at ThreadTest$1.run(ThreadTest.java:34)

编辑:这是与@peter-lawrey 讨论后获得的解决方案

public class StackTraceInheritingThread {

    private final Runnable r;

    private volatile Thread th = null;
    private String title;
    private boolean daemon;

    private InheritedStackTrace ist ;

    private static final ThreadLocal<InheritedStackTrace> tl = new ThreadLocal<InheritedStackTrace>();

    public StackTraceInheritingThread(Runnable r) {
        this.r = r;
    }

    private final class StackTraceInheritingUncaughtExceptionHandler implements  Thread.UncaughtExceptionHandler {

        @Override public void uncaughtException(Thread t, Throwable e) {
            if(ist!=null){ 
                e.addSuppressed(ist);
            }
            e.printStackTrace(System.err);
        }

    }

    public StackTraceInheritingThread setName(String nm){
        this.title = nm;
        return this;
    }

    public StackTraceInheritingThread setDaemon(boolean daemon) {
        this.daemon = daemon;
        return this;
    }

    public void start(){
        if(th!=null){
            throw new IllegalStateException("Already started");
        }

        th = new Thread(new Runnable() {
            @Override public void run() {
                tl.set(ist);
                r.run();
            }
        },title);
        th.setUncaughtExceptionHandler(new StackTraceInheritingUncaughtExceptionHandler());
        if(daemon)th.setDaemon(true);
        ist = new InheritedStackTrace();
        th.start();
    }

    public static Throwable getInheritedStackTrace(){
        return tl.get();
    }

    public static StackTraceInheritingThread make(Runnable r1){
        return new StackTraceInheritingThread(r1);
    }


    private static final class InheritedStackTrace extends Exception {

    }


    public static void main(String[] args) {
        StackTraceInheritingThread.make(new Runnable() {

            @Override
            public void run() {
                System.out.println("heelo");
                throw new RuntimeException();
            }
        }).setName("ExperimentalThread").start();
    }
}

【问题讨论】:

    标签: java multithreading debugging stack-trace printstacktrace


    【解决方案1】:

    您可以将用于创建线程的 Throwable 保存在线程局部变量中。

    public enum Throwables {
        ;
    
        private static final InheritableThreadLocal<Throwable> STARTING_THREAD = new InheritableThreadLocal<>();
    
        public static void set(Throwable t) {
            STARTING_THREAD.set(t);
        }
    
        public static Throwable get() {
            return STARTING_THREAD.get();
        }
    
        public static void printStartingThrowable() {
            Throwable throwable = get();
            if (throwable == null) return;
            throwable.printStackTrace();
        }
    
        public static Thread start(Runnable run, String name, boolean daemon) {
            Throwable tmp = new Throwable("Started here");
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    set(tmp);
                    run.run();
                }
            }, name);
            t.setDaemon(daemon);
            t.start();
            return t;
        }
    
        public static void main(String... ignored) {
            try {
                method();
            } catch (Throwable t) {
                System.err.println("\nThrown in " + Thread.currentThread());
                t.printStackTrace();
                printStartingThrowable();
            }
    
            start(new Runnable() {
                @Override
                public void run() {
                    try {
                        method();
                    } catch (Throwable t) {
                        System.err.println("\nThrown in " + Thread.currentThread());
                        t.printStackTrace();
                        printStartingThrowable();
                    }
                }
            }, "Test thread", false);
    
        }
    
        private static void method() {
            throw new UnsupportedOperationException();
        }
    }
    

    打印

    Thrown in Thread[main,5,main]
    java.lang.UnsupportedOperationException
        at Throwables.method(Throwables.java:59)
        at Throwables.main(Throwables.java:36)
    
    Thrown in Thread[Test thread,5,main]
    java.lang.UnsupportedOperationException
        at Throwables.method(Throwables.java:59)
        at Throwables.access$000(Throwables.java:1)
        at Throwables$2.run(Throwables.java:47)
        at Throwables$1.run(Throwables.java:26)
        at java.lang.Thread.run(Thread.java:744)
    java.lang.Throwable: Started here
        at Throwables.start(Throwables.java:21)
        at Throwables.main(Throwables.java:43)
    

    【讨论】:

    • 您所建议的必须手动完成。想象一下必须更改的代码量。并且在没有被捕获时抛出异常。这意味着,我事先不知道异常会在哪一行被抛出。我需要的是,无论在被调用线程中抛出异常的位置如何,调用者的堆栈跟踪也都附加到它上面。我希望这是有道理的。
    • @ShashankTulsyan 扔到哪里无关紧要,只在打印的地方。您可以添加一个 handleExcdeption(t) 方法,这样添加调用者的代码只会出现在一个地方。
    • 这适用于未捕获的异常。您还可以为捕获的异常设置一个本地线程。
    • @ShashankTulsyan 添加了一个示例,其中相同的代码是否可以在启动的线程中工作。
    • 相比于启动线程的成本甚至是长日志消息的成本,性能影响很小。如果您希望多次打印堆栈跟踪,则可以将堆栈跟踪缓存为字符串。我还会修剪打印的堆栈跟踪以提高性能和可读性。
    猜你喜欢
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 2013-01-19
    • 2018-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-22
    相关资源
    最近更新 更多