Java 规范 17.5 具有以下代码来说明 Java 内存模型中最终字段的使用。(与普通字段相比)
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。一个线程可能执行方法编写器,另一个线程可能执行方法读取器。由于编写器方法在对象的构造函数完成后写入 f,因此可以保证读取器方法看到 f.x 的正确初始化值:它将读取值 3。但是,f.y 不是最终的;因此,不能保证读取器方法看到它的值 4。
我的问题是:这难道不是一个蹩脚的(或者至少是一个拙劣的)例子吗?或者我在这里错过了什么?
我把这个例子称为“跛脚”的理由是:
如果FinalFieldExample类的对象要在多线程场景中由线程共享,那么它是否应该遵循多线程的基本原则,即使用某种形式的同步。如果他们使用了同步,那么上述问题将不存在。
上面的例子似乎提倡最终字段作为正确同步技术的替代方案(或部分安抚器)。在我的理解中,最终字段即使在正确同步的基础上使用也有用。并且永远不应该被用来获得示例中提到的优势(在没有同步的情况下)。
所以有人会问:难道没有一个像样的例子(同步)来解释最终场相对于普通场的优势吗?我想,不可变是!
您混淆了<code>同步</code>和并发。
如果字段是常量,则可以在多个线程
之间安全地共享该字段,而无需锁定。
如果字段是变量,则需要同步
或以其他方式锁定。
您可以有一个并发程序,该程序具有多个线程读取相同的常量字段,这不会阻止任何线程
。
任何使用< code>synchronized块的代码都要付出巨大的代价。这是一个非常昂贵的过程,应该尽可能避免。更不用说资源饥饿、死锁、活锁等问题了。等等...
如果可以使用final
而不是synchronized
,那么应该这样做。
编辑:我错过了这个回答的重点。问题不在于价值可以改变。改为看bmorris591的回答。
不可变对象的优点之一是您不需要同步。
但是这个例子不是关于同步,而是关于保证读者线程看到的值。即使有了同步,< code>y的值也可能改变,而< code>x的值总是保证为3。
你参考的这个规范只是描述了这些东西的行为。基于此规范,您可以决定如何正确编码。这个例子绝不试图代表真实的用例。它只是用几行文字说明了行为是什么。如果您的jvm实现没有这样的行为,那么它就是一个bug。