如何使公共静态非同步getInstance()方法将私有静态引用变量的多个实例返回给对象?

时间:2013-12-17 20:38:48

标签: java multithreading thread-safety synchronized scjp

我跑过的一个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());
    }
}

4 个答案:

答案 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());
    }
}