为什么Throwable类的以下方法需要同步?

问题描述

private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
private transient Object backtrace;

private synchronized StackTraceElement[] getourStackTrace() {
    // Initialize stack trace field with information from
    // backtrace if this is the first call to this method
    if (stackTrace == UNASSIGNED_STACK ||
        (stackTrace == null && backtrace != null) /* Out of protocol state */) {
        int depth = getStackTraceDepth();
        stackTrace = new StackTraceElement[depth];
        for (int i=0; i < depth; i++)
            stackTrace[i] = getStackTraceElement(i);
    } else if (stackTrace == null) {
        return UNASSIGNED_STACK;
    }
    return stackTrace;
}

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null ||
        backtrace != null /* Out of protocol state */ ) {
        fillInStackTrace(0);
        stackTrace = UNASSIGNED_STACK;
    }
    return this;
}

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null ||
        backtrace != null /* Out of protocol state */ ) {
        fillInStackTrace(0);
        stackTrace = UNASSIGNED_STACK;
    }
    return this;
}

以上代码为jdk源码

jdk 版本:1.8.0_144

异常栈不应该是线程私有的吗?为什么需要同步控制?

解决方法

为什么Throwable类的以下方法需要同步?

出于正常原因。可能存在两个线程同时执行触发这些调用中的任何一个的操作的情况。如果它们不是 synchronized,则可能会导致竞争条件或内存异常。

(请注意,stacktracebacktrace 都不是 volatile,因此如果代码测试和/或从多个线程分配它们,则某些线程可能会看到过时的值。)

现在对所有可能的代码路径进行深入分析可能表明,如果不声明这些方法 synchronized,此代码将是线程安全的。但如果是我编写代码,我可能还是会使用 synchronized,因为:

  • 在这里使用synchronized显然是安全的,并且
  • 相对于捕获异常的堆栈跟踪时发生的所有其他事情,(可能是不必要的)互斥锁的开销很小,并且
  • 异常应该很少出现……除非程序错误地使用了异常。

如果获取异常的堆栈跟踪不是线程安全的,那将是不可接受的。如果堆栈跟踪不可靠,调试 Java 将完全是“废话”。这是代码必须是线程安全的情况之一,即使规范(javadoc)对此保持沉默。