Java中的volatile与threadLocal的区别

6

让我们以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;

并且实现相同的线程安全性?

1
因为这不是线程安全问题发生的地方:线程安全问题在于SimpleDateFormat具有可变状态,这与引用是否存储在易失性字段中无关。 - Andy Turner
3
本地线程和 volatile 并不相同!使用本地线程,每个线程都有变量的单独副本。而使用 volatile,所有线程共享一个变量。将变量设为 volatile 并不能使其线程安全,因为 SimpleDateFormat 存在内部状态,不应由多个线程同时更新,正如 Andy 所提到的。 - Jesper
3个回答

6

volatile关键字保证线程拥有变量的最新副本。

仅限于volatile变量而非其字段。

此外,只有在需要更改变量值时,volatile才是有用的。在您的使用情况下,final看起来更加适合:

private static final SimpleDateFormat format = ...

这也保证了您将拥有变量的最新值 - 因为它只能被赋值一次,而static final保证了在类完全加载后的可见性。
但这并不是SimpleDateFormat不可线程安全的原因:它具有可变状态,用于在格式化日期时存储中间值。
如果一个线程调用format,同时另一个线程也在使用同一个SimpleDateFormatter实例的format方法,则这些中间变量会被无法预测地覆盖,导致线程之间产生干扰,从而产生不可预测的输出。
无论这些中间变量的值在被另一个线程读取/写入时是否是最新的都无关紧要 - 它们的更新可以相互交错。
简而言之,volatile不能防止线程干扰,在这里不是适当替代ThreadLocal的选择。

1
仅限于volatile变量本身,而不是它的字段。这实际上是不精确的,甚至是误导性的,因为您将观察到写入线程在写入volatile字段之前对所引用对象的字段进行的所有写操作。这就是安全发布的本质。 - Marko Topolnik
@MarkoTopolnik 真的吗?我想的是类似于 System.out.println(volatileField.nonVolatileField); 这样的东西 - 读取 volatileField 可能会让你看到到目前为止对 nonVolatileField 的所有写入;但是在读取 volatileFieldvolatileField.nonVolatileField 之间,nonVolatileField 是否可以被更新呢? - Andy Turner
是的,这就是为什么我说你的措辞是“误导性的”,而不是“不正确的”。听起来好像你除了volatile引用本身之外没有任何保证。另一方面,你也不能保证观察到volatile字段的“最新”状态,因为“最新”甚至在JMM中都不是一个明确定义的概念。本质上的区别在于顺序一致性与线性化。 - Marko Topolnik

1

ThreadLocal 是一种机制,它使线程可以拥有自己的本地对象副本。ThreadLocals 最好与可以在线程安全策略下实现线程安全的对象一起使用(即使对于许多不是“线程安全”的对象,只要没有引用从限制线程泄漏出来,仍然可以实现线程安全使用)。ThreadLocals 无法帮助可变对象在实例化它们的线程之外的线程中进行线程安全使用。

volatile 关键字用于提供一种弱形式的线程安全,用于可能被许多不同线程访问的变量。一个关键区别是 ThreadLocals 大多数情况下不会被超过一个线程访问。

广义上讲,线程安全需要可见性(变量的最新更新应该对其他线程可见)和互斥性(状态转换必须是原子的,以便状态不能被观察为不一致)。volatile 与 Java 内存模型一起工作,保证变量将是可见的,但它不提供任何形式的互斥性,因此不会为对象中的状态转换提供原子性。

因为 volatileThreadLocal 是如此不同,所以在没有共同情况的情况下,你无法用一个来替换另一个。

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接