public class Foo {
private static Foo foo;
private Foo(){}
public static Foo getInstance(){
if (foo==null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
foo = new Foo();
System.out.println(foo+"----"+Thread.currentThread().getName());
}
return foo;
}
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Foo foo1 = Foo.getInstance();
}
},"thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Foo foo2 = Foo.getInstance();
}
},"thread2");
thread1.start();
thread2.start();
}
}
我只是想在多线程环境中模拟这些代码是不安全的,但它总是输出两个相同的对象,如:
Foo@24c21495----thread1
Foo@24c21495----thread2
或
Foo@3d4b7453----thread1
Foo@3d4b7453----thread2
...
为什么?
答案 0 :(得分:5)
我相信这里有两个综合原因:
基本上,我相信两个线程都在创建新实例,然后两个都显示结果 - 除非你“幸运”,否则两个线程都会看到任何线程执行第二个任务的结果。
如果您只是添加:
System.out.println("Starting");
到main
的最开头,你有时会看到不同的输出。如果你也把你的睡眠改为:
Thread.sleep((long) (Math.random() * 100));
...那么你更有可能看到它。基本上,您希望一个线程在另一个线程处于休眠状态时完成整个“睡眠,初始化,打印”。
另一种选择是简单地向构造函数添加一个print语句:即使两个线程在当前诊断位置显示相同的对象,您仍然会看到构造的两个实例,这表明代码是不安全的。
答案 1 :(得分:0)
因为sleep(1)
只能睡1毫秒。当第二个帖子点击getInstance
时,您的foo
已经初始化。
你可能想要的是调用synchronized
init函数,在设置之前检查foo
是否为null。根本不需要睡觉。它仍将输出相同的内容,但将是一个线程安全的初始化程序。
示例:
public static Foo getInstance () {
if( foo == null )
initFoo();
return foo;
}
private static synchronized void initFoo () {
if( foo == null )
foo = new Foo();
}
答案 2 :(得分:0)
我相信在一个classloader
内,static fields
始终是共享的。
答案 3 :(得分:0)
如果你想要一个“单线程每个线程”,你可能想看看ThreadLocal [1]。那么你也不需要正确地同步调用getInstance。
[1] http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html