ThreadLocal.get() 返回 null,即使我之前对其进行了初始化

ThreadLocal.get() returning null, even when I'm initializing it before

我觉得我可能误解了它的工作原理。

我有这个代码:

public Timestamp startTransaction() {
    cleanupTransactionContext();
    Timestamp timestamp = getLatestTimestamp();
    initThreadLocalVariables(timestamp);
    return getTransactionContext().toString();
}

private Timestamp getTransactionContext() {
    if (transactionContext == null) {
        throw new BadTransactionStateException();
    }
    return transactionContext.get();
}

private void initThreadLocalVariables(Timestamp timestamp) {
    setTransactionContext(timestamp);
}

private void setTransactionContext(Timestamp ts) {
    if (this.transactionContext == null) {
        this.transactionContext = new ThreadLocal<>();
    }
    this.transactionContext.set(ts);
}

据我了解,ThreadLocal.get() 永远不应 return null(来自 JDK):

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

因为我之前在 setTransactionContext 中明确设置了它,这又会调用 ThreadLocal.set,这应该是创建地图:

   /**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

但是,有时我会在 return getTransactionContext().toString(); 中收到空指针异常。其他时候它工作得很好,所以我怀疑某种竞争条件,我只是看不出它可能是什么。

PS:时间戳 class 如下所示:

public final class Timestamp {

private final long timeInMilliseconds;
private final long sequenceNumber;

}

但请注意,这是代码的简化 版本,不包括多项检查以确保其不为空。 getLatestTimeStamp 值本身是正确的,不会设置为空。

正如@shmosel 所指出的——问题是这段代码不是原子的:

private void setTransactionContext(Timestamp ts) {
  if (this.transactionContext == null) {
    this.transactionContext = new ThreadLocal<>();
  }
  this.transactionContext.set(ts);
}

所以两个线程可能正在创建 ThreadLocal 并相互干扰。将本地线程的创建移动到变量声明可以解决问题,因为默认情况下对 ThreadLocal 的后续操作是线程安全的。