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

时间:2019-01-29 21:01:44

标签: java multithreading optimistic-concurrency

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

我有此代码:

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()绝不应返回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:Timestamp类如下:

public final class Timestamp {

private final long timeInMilliseconds;
private final long sequenceNumber;

}

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

1 个答案:

答案 0 :(得分:1)

@shmosel指出-问题是这段代码不是原子的:

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

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