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 的后续操作是线程安全的。
我觉得我可能误解了它的工作原理。
我有这个代码:
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 的后续操作是线程安全的。