我想测试延迟初始化是否是线程安全的

时间:2013-05-12 08:11:56

标签: java thread-safety race-condition

我想测试延迟初始化是否是线程安全的,所以我的代码如下:

package LazyInit;

import java.util.Random;

public class UnThreadSafeLazyInit {

    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (null == instance) {
            instance = new ExpensiveObject();
        }
        System.out.println("instance=" + instance);
        return instance;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub


        for (int i = 0; i < 5; i++) {
            UnThreadSafeLazyInit  init = new UnThreadSafeLazyInit();
            Task t1 = init.new Task();
            Task t2 = init.new Task();
            t1.start();
            t2.start();
            try {
                Thread.sleep(4000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(t1.getInstance() == t2.getInstance());
        }
    }

    static class ExpensiveObject {

    }

    class Task extends Thread {

        private ExpensiveObject instance = null;
        private Random rand = new Random(47);

        public void setInstance () {
            this.instance = UnThreadSafeLazyInit.this.getInstance();
        }

        public ExpensiveObject getInstance() {
            return instance;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub

            try {
                Thread.sleep(rand.nextInt(1000));
            } catch (Exception e) {
                e.printStackTrace();
            }
            setInstance();
        }
    }

}

在我的代码中,每当我new两个Thead任务调用public ExpensiveObject getInstance()时,为了证明两个instance可能与竞争条件后的ExpensiveObject不同。 当我运行它时,它总是由true返回t1.getInstance() == t1.getInstance()。 据我所知,如果我不synchronized函数public ExpensiveObject getInstance(),它可能会返回false,因为在Lazy Initialization中存在种族条件。 我需要找出哪个代码是错误的。 谢谢。

4 个答案:

答案 0 :(得分:2)

通过检查代码,它不是线程安全的。你遇到的问题是延迟几毫秒是计算机的一个巨大时间,你很可能不会发现这种类型的测试有问题。

例如,更新易失性字段与其他线程可见之间的典型延迟约为5纳秒。大约这么久,您的解决方案不是线程安全的。您正在等待高达1,000,000,000纳秒,以查看是否出现问题。

这就像试图查看烟花持续5秒钟是否会消失,但在结束之前317年闭上眼睛没有烟火。

答案 1 :(得分:1)

其他人已经涵盖了为什么它不是线程安全的。但我想对你的标题发表评论:“我想测试延迟初始化是否是线程安全的”。

您无法测试一段代码是否是线程安全的。您可能能够找到一个证明它不是的测试,但测试只能证明线程安全:

  • 您的测试可能不会以重现问题的方式交错线程
  • 您的测试可能会引入隐藏实际问题的其他同步(例如System.out.println已同步)
  • 问题可能只出现在极少数情况下,可能不会遇到一些测试运行
  • 问题可能只出现在某些JVM / CPU上,并且您的测试“与某个特定设置一起工作”这一事实无论如何都无法证明

答案 2 :(得分:0)

最简单的方法是使ExpensiveObject成为一个非常昂贵的对象:

public class ExpensiveObject {
    public ExpensiveObject() {
        System.out.println("I'm expensive!");
        try {
            Thread.sleep(2000L);
        }
        catch (InterruptedException e) {
        }
        System.out.println("See. It took 2 seconds to create me!");
    }
}

否则,进入rece条件的可能性非常小,特别是因为一个线程在另一个线程之后启动,因此在另一个线程之后调用setInstance()

答案 3 :(得分:0)

这不是线程安全的。你这次很幸运。修改你的代码:

public ExpensiveObject getInstance() {
    if (null == instance) {
        System.out.println("old instance=" + instance);
        instance = new ExpensiveObject();
        System.out.println("new instance=" + instance);
    }
    return instance;
}
// In main
Thread.sleep(40); // Thread.sleep(4000);
// In run
Thread.sleep(rand.nextInt(10)); // Thread.sleep(rand.nextInt(1000));

我在控制台中看到了很多false的代码。