ThreadLocal.get () возвращает ноль, даже когда я его инициализирую раньше - PullRequest
0 голосов
/ 30 января 2019

Мне кажется, что я не понимаю, как это должно работать.

У меня есть этот код:

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 () никогда не должен возвращать ноль (из 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;

}

Нообратите внимание, что это упрощенная версия кода, которая не включает в себя несколько проверок, чтобы убедиться, что она не равна нулю.Само значение getLatestTimeStamp является правильным и не будет равно нулю.

1 Ответ

0 голосов
/ 31 января 2019

Как указал @shmosel, проблема была в том, что этот фрагмент кода не был атомарным:

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

Таким образом, два потока могут создавать ThreadLocal и мешать друг другу.Перемещение создания потока, локального для объявления переменной, решает проблему, так как последующие операции над ThreadLocal по умолчанию безопасны для потока.

...