java 中的 volatile 与 threadLocal

volatile vs threadLocal in java

让我们以 SimpleDateFormat 为例,因为它不是线程安全的。

我可以像这样使用 threadLocal 允许每个线程拥有自己的 SimpleDateFormat 副本:

 private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
    @Override
    protected SimpleDateFormat initialValue()
    {
        return new SimpleDateFormat("yyyyMMdd HHmm");
    }
};

但是 volatile 关键字保证线程将拥有变量的最新副本。那么我可以不这样做吗:

volatile SimpleDateFormat myformatter;

和实现同样的线程安全?

您无法使用 volatile 实现相同的线程安全,因为 SimpleDateFormat 的相同实例将在不同的线程中使用。

the volatile keyword guarantees that a thread will have the most recent copy of the variable

只有 volatile 变量 ,不是它的字段。

此外,volatile 仅在需要更改变量值时才有用。在您的用例中,final 看起来更合适:

private static final SimpleDateFormat format = ...

这也保证您将拥有变量的最新值 - 因为它只能被赋值一次,并且 static final 保证了 class 完全加载后的可见性.


但这并不是 SimpleDateFormat 无论如何都不是线程安全的原因:它具有可变状态,用于在格式化日期时存储中间值。

如果一个线程调用 format 而另一个线程也在同一 SimpleDateFormatter 实例的 format 方法中,这些中间变量会不可预测地被踩踏,导致线程之间的干扰,从而产生不可预测的输出。

当 read/written 被另一个线程更新时,这些中间变量的值是否是最新的并不重要 - 它们的更新可以穿插进行。

简而言之,volatile 不能防止线程干扰,因此这里不是 ThreadLocal 的适当替代。

ThreadLocal 是一种使线程能够拥有自己的对象本地副本的工具。 ThreadLocals 最好与在 线程限制 的线程安全策略中可以是线程安全的对象一起使用(即使对于许多不是 "thread safe" 的对象,线程安全使用仍然是可能的,因为只要没有对它们的引用从限制线程中泄漏出来)。 ThreadLocals 无法帮助在实例化它们的线程之外共享的可变对象的线程安全使用。

volatile 关键字用于提供弱形式的线程安全,其中包含一个可以被许多不同线程访问的变量。一个关键区别是 ThreadLocal 通常不会被多个线程访问。

从广义上讲,线程安全需要可见性(变量的最新更新应该对其他线程可见)和互斥性(状态转换必须是原子的,这样才能观察到状态不一致)。 Volatile 使用 Java 内存模型来保证变量可见,但它不提供任何形式的互斥,因此不为对象中的状态转换提供原子性。

因为 volatileThreadLocal 是如此不同,所以确实没有常见的情况可以用一个代替另一个。