Добавить стек трассировки потока вызывающей стороны в создаваемый новый поток для упрощения отладки
Я часто сталкивался с проблемой при отладке.
Иногда поток заканчивается, выбрасывая исключение.
И причина этой проблемы - вызывающая сторона / стартер потока.
Вызывающий отправил неверный параметр или вызвал поток без инициализации чего-либо.
Чтобы найти, откуда был вызван конкретный поток, требуется немного дополнительных усилий, так как трассировка стека бесполезна.
Что если бы мы могли добавить трассировку стека потока вызывающей стороны в вызываемый поток.
Рассмотрим следующий пример:
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();
}
}
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)