我跑过的一个SCJP练习题提供了SafeDeposit类中的代码。该问题的答案声称,如果另一个类使用多个线程,则非同步(非线程安全)getInstance()方法可能返回多个SafeDeposit实例。我已经尝试过,并尝试并且无法获取toString()方法来指示创建了多个SafeDeposit实例。我是否遗漏了某些东西,或者这只是“可能”发生的事情之一但是真的,真的,真的不太可能发生吗?
class SafeDeposit {
private static SafeDeposit sd;
public static SafeDeposit getInstance() {
if(sd == null) sd = new SafeDeposit();
return sd;
}
private SafeDeposit() { }
}
public class PrivCon {
public static void main(String[] args) {
String checker;
SafeThief wizard = new SafeThief();
SafeThief wizard2 = new SafeThief();
for(int i = 0; i < 10; i ++) {
new Thread(wizard).start();
new Thread(wizard2).start();
}
}
}
class SafeThief implements Runnable {
public void run() {
System.out.println(SafeDeposit.getInstance().toString());
}
}
答案 0 :(得分:4)
这只是“可能”发生的事情之一但是真的,真的,真的不太可能发生吗?
试试这段代码,看看它真的不太可能:
class SafeDeposit {
private static SafeDeposit sd;
public static SafeDeposit getInstance() {
if(sd == null) sd = new SafeDeposit();
return sd;
}
private SafeDeposit() { }
static void warmup() {
for (int i = 0; i < 100_000; i++) getInstance();
sd = null;
}
}
public class PrivCon {
public static void main(String[] args) {
SafeDeposit.warmup();
SafeThief wizard = new SafeThief();
for(int i = 0; i < 10; i ++) new Thread(wizard).start();
}
}
class SafeThief implements Runnable {
public void run() {
try { Thread.sleep(100); } catch (InterruptedException e) { }
System.out.println(SafeDeposit.getInstance().toString());
}
}
这是我的典型输出:
test.SafeDeposit@52e5376a
test.SafeDeposit@34780af5
test.SafeDeposit@351775bc
test.SafeDeposit@2b1be57f
test.SafeDeposit@6ae6235d
test.SafeDeposit@6276e1db
test.SafeDeposit@52e5376a
test.SafeDeposit@302b2c81
test.SafeDeposit@60f00e0f
test.SafeDeposit@1732a4df
几乎没有任何重复。
如果你想知道为什么,那是因为我添加了热身代码,这导致getInstance()
方法被JIT编译成一个积极优化的代码片段,利用给定的自由通过Java Memory Model。
我还在sleep
的开头添加了一些Runnable
时间,因为只要一个线程写入该值,那些在该点之后开始的线程就会可靠地观察到写入。所以最好先让所有线程启动,然后让它们调用getInstance
。
答案 1 :(得分:1)
正确。这是 NOT 线程安全,
if(sd == null) // Thread B here <---
sd = new SafeDeposit(); // Thread A here <---
return sd;
因此,如果你有如上所述的线程A和B,你将获得两个实例化的Singleton实例。要查看它,请在构造函数中添加一个print方法,如this =
private SafeDeposit() {
System.out.println("In SafeDeposit constructor - Should only print ONCE");
try {
Thread.sleep(2000); // <-- Added to help reproduce multiple
// instances being created.
} catch (Exception e) {
}
}
答案 2 :(得分:0)
SafeDeposit
构造函数在您的代码中以原子方式运行,您没有看到问题。要模拟更真实的情况,请将SafeDeposit
构造函数更改为下面的代码,您将自己查看结果。
private SafeDeposit() {
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {}
}
答案 3 :(得分:0)
强调单身人士的方法是使用CountDownLatch
让一大堆线程同时下降。遗憾的是,这段代码无法打印1
以外的任何内容,但我怀疑这是因为我在单核笔记本电脑上测试它。有人会在多核CPU上测试它,看看它是否打印了其他内容?
请参阅以下评论,了解返回结果的测试结果&gt; 1表示实际创建了多个假定单例的实例。
public class Test {
static class SafeDeposit {
private static SafeDeposit sd;
public static SafeDeposit getInstance() {
if (sd == null) {
sd = new SafeDeposit();
}
return sd;
}
private SafeDeposit() {
}
}
static final Set<SafeDeposit> deposits = Collections.newSetFromMap(new ConcurrentHashMap<SafeDeposit,Boolean>());
static class Gun implements Runnable {
private final CountDownLatch wait;
public Gun (CountDownLatch wait) {
this.wait = wait;
}
@Override
public void run() {
try {
// One more thread here and ready.
wait.countDown();
// Wait for the starting pistol.
wait.await();
// Grab an instance - nnnnnnnnow!!!.
SafeDeposit safe = SafeDeposit.getInstance();
// Store it in the Set.
deposits.add(safe);
} catch (InterruptedException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
// Use that many Threads
private static final int ArmySize = 1000;
public static void main(String[] args) throws InterruptedException {
// The Latch will wait for all threads to be ready.
CountDownLatch latch = new CountDownLatch(ArmySize);
Thread[] threads = new Thread[ArmySize];
for ( int i = 0; i < ArmySize; i++ ) {
// Make all threads and start them.
threads[i] = new Thread(new Gun(latch));
threads[i].start();
}
// Wait for all to complete.
for ( int i = 0; i < ArmySize; i++ ) {
threads[i].join();
}
// How many unique Safes did we et?
System.out.println(deposits.size());
}
}