我读过this文章,声称使用java 1.5的静态成员是线程安全的。它是否也意味着只有一个成员实例或不同的线程可以缓存不同的实例?
例如:
public class MyClass {
private static Foo foo = null;
public String getFoo()
{
if (foo== null)
foo= new Foo();
//init foo...
}
}
如果我有多个线程可以:
String foo = (new MyClass()).getFoo();
我猜所有的线程都会得到相同的“init”foo但foo会初始化多少次?如果我将添加volatile并将foo定义为“public static volatile Foo ...”,它会改变什么吗?
答案 0 :(得分:2)
我读过这篇文章,声称使用java 1.5的静态成员是线程安全的。
更具体地说,Java Memory Model FAQ特别指出:
如果在静态初始值设定项中设置了字段,则可以保证它可以正确显示到访问该类的任何线程
在您发布的代码中,您的static
字段在静态初始值设定项中未设置 。如果它执行了以下操作,则不存在线程安全问题:
private static Foo foo = new Foo();
然而,在static
字段的大多数其他情况下,线程安全性是一个问题:
换句话说,不要在另一个线程可能看到它的地方放置对正在构造的对象的引用;不要将其分配给静态字段,不要将其注册为任何其他对象的侦听器,依此类推。
您的其他问题:
我猜所有线程都会得到相同的“init”Foo
无法保证。两个线程可以同时调用getFoo()
并确保获得Foo
的不同实例。
但foo会初始化多少次?
这是未知的,取决于正在运行的线程数,数据发布等。
如果我将添加volatile并将foo定义为“public static volatile Foo ...”,它会改变什么吗?
当它被分配给静态字段时,它将确保至少Foo
将被正确构造,但它不会再次保护您在多个线程调用getFoo()
时发生的竞争条件同时。
通常,当volatile static
的构造很便宜时,我将使用Foo
字段模式,我不介意其中一个线程是否使用其Foo
的实例然后它是垃圾收集。我只是希望其余大部分操作都使用相同的实例。
另一种模式是执行以下操作:
private static final AtomicReference<Foo> fooRef = new AtomicReference<Foo>();
public static String getFoo() {
Foo foo = fooRef.get();
if (foo != null) {
return foo;
}
foo = new Foo();
if (fooRef.compareAndSet(null, foo)) {
return foo;
} else {
// foo ref was set by another thread so our Foo is not used
return fooRef.get();
}
}
这可能会创建并丢弃一些Foo
的额外实例,但至少所有线程都将使用Foo
的相同实例。
但是,如果必须只有一个Foo
实例并且您无法在静态初始化器中构造它(见上文),那么您将被迫锁定其构造。< / p>
答案 1 :(得分:1)
每个线程都可以看到null
,每个线程都可以创建它自己的实例。使用volatile
会降低这种可能性,但仍然可能发生。
您需要做的是使用一些同步或静态初始化(这是线程安全的)
public class MyClass {
private static final Foo foo = new Foo();
public static String getFoo() { return foo; }
}
或
public class MyClass {
private static Foo foo = null;
public static synchronized String getFoo()
{
if (foo== null)
foo= new Foo();
//init foo...
}
}
对于Java 5.0和所有其他版本也是如此。