我到底什么时候需要同步 ThreadLocal.initialValue()?

When exactly do I need to synchronize ThreadLocal.initialValue()?

我多次读到 ThreadLocal 唯一可能有争议的部分(关于同步)是 ThreadLocal.withInitial() 调用,因为 initialValue() 方法总是被调用每 Thread 访问一次 ThreadLocal.

到目前为止一切顺利。但是假设我有以下请求生命周期增强器:

public class MyLifecycleEnhancer extends RequestLifecycleEventHandler {

    private static ThreadLocal<Map<String, Object>> currentRequestContext = ThreadLocal.withInitial(HashMap::new);

    public void onRead(...) {
        Map<String, Object> context = currentRequestContext.get(); // calls initialValue()
        // put something in context
    }

    public void onDone(...) {
        Map<String, Object> context = currentRequestContext.get(); // does not call initialValue(), though it may be being called in another thread
        // do something with context
        currentRequestContext.remove();
    }

    // other lifecycle hooks 
}

据我所知,此示例中唯一的争论点可能在 onDone() 方法中,如:

  1. 查询ThreadLocal
  2. ThreadLocal initialValue() 可能正在被另一个 Thread 调用。

但这对我来说意义不大。一旦 currentRequestContext.get() 被调用 一次 (隐式调用 initialValue(),如果这是 get() 第一次被调用),该值将被存储在当前线程的 ThreadLocalsMap 中并且仅对该特定线程可见。在这种情况下,调用 initialValue() 的其他线程应该无关紧要。

我错过了什么? initialValue() 何时、如何以及为什么要进行同步?


编辑:

提到的上述同步注意事项的具体示例是 ,特别是@MargaretBloom 的评论:

Exactly. The only source of race condition I can think of is the method initialValue of ThreadLocal. This is shared across all the threads and, as seen in the example in the doc linked, it must use some protection. This method is called by get since they always perform a remove after it (don't know why)

这种同步问题是否只适用于初始值可变的情况(在我的示例中不是这种情况)?还是我误会了什么?

如果您的 withInitial 供应商正在 returning 自包含对象仅供当前线程使用,并且 Hashmap::new 为每个线程提供一个新对象等等,则无需担心很好。

如果供应商本身不是线程安全的,或者它 returns 的对象不是线程安全的,您可能会使事情变得尴尬。以下是不良供应商的一些示例:

供应商提供相同的映射,如果跨线程使用则不是线程安全的:

static Map<String, Object> MAP = new HashMap<>();
private static ThreadLocal<Map<String, Object>> tls = ThreadLocal.withInitial(() -> MAP);

供应商的主体不是线程安全的方式来确定每个线程的唯一值,如果使用 get() 调用可能 return 重复:

static int unique = 0;
private static ThreadLocal<String> tls = ThreadLocal.withInitial(() -> "IDENIFIER#"+(unique++));