1

Java 规范17.5有以下代码来说明 Java 内存模型中 final 字段的使用。(与普通字段相比)

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    } 

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

规范继续说:

“类 FinalFieldExample 有一个最终 int 字段 x 和一个非最终 int 字段 y。一个线程可能执行方法 writer,另一个线程可能执行方法 reader。因为 writer 方法在对象的构造函数完成后写入 f,reader 方法将保证看到 fx 的正确初始化值:它将读取值 3。但是,fy 不是最终的;因此不能保证读取器方法看到它的值 4。

我的问题是:这不是一个蹩脚的(或至少是人为的)例子吗? 还是我在这里遗漏了什么?

我将这个例子称为“蹩脚”的理由是:

如果FinalFieldExample 类的对象要在多线程场景中被线程共享,它不应该遵循多线程的基本原则,即使用某种形式的同步。如果他们使用了同步,那么上面提到的问题就不会存在。

上面的示例似乎提倡将 Final 字段作为适当同步技术的替代方案(或部分安慰者)。据我了解,即使在正确同步的基础上使用 final 字段也是有用的。并且永远不应该用于获得示例中提到的优势(在没有同步的情况下)。

所以有人可能会问:难道没有一个像样的例子(带有同步)来解释 final 字段相对于普通字段的优势吗?我想,不变性是!

4

3 回答 3

4

你是混乱synchronization和并发的。

如果一个字段是一个常量,那么它可以在多个Threada 之间安全地共享,而无需任何锁定。

如果一个字段是一个变量,那么它需要被synchronized锁定或以其他方式锁定。

你可以有一个并发程序,它有多个线程读取相同的常量字段,这不会阻塞任何Threads。

任何使用synchronized块的代码都会付出巨大的代价。这是一个非常昂贵的过程,应尽可能避免。更不用说资源匮乏、死锁、活锁等问题......

如果你可以使用final而不是synchronized你应该这样做。

于 2013-04-12T21:08:35.017 回答
3

编辑:我错过了这个答案的重点。问题不在于可以更改该值。请参阅 bmorris591 的答案。

不可变对象的优点之一是您不需要同步。

但是这个例子不是关于同步的,而是关于读者线程保证看到的值。即使同步, 的值也y可能发生变化,而 的值x始终保证为 3。

于 2013-04-12T21:07:04.423 回答
1

您所指的这个规范只是描述了东西(应该)如何表现。根据此规范,您可以决定如何正确编码。这个例子绝不试图代表一个真实的用例。它只是用几行来说明行为是什么。如果你的 jvm 实现不这样,那么它就是一个错误。

于 2013-04-12T21:14:08.430 回答