证明以下代码不是线程安全的

时间:2010-03-09 16:12:45

标签: java multithreading thread-safety

如何通过编写一些代码来快速证明以下类是不是线程安全的(因为它使用了惰性初始化而不是使用同步)? 换句话说,如果我正在测试以下类的线程安全性,我该怎么办呢?

public class LazyInitRace {
  private ExpensiveObject instance = null;

  public ExpensiveObject getInstance() {
     if (instance == null)
        instance = new ExpensiveObject();
    return instance;
  }
}

8 个答案:

答案 0 :(得分:15)

根据定义,除非您控制线程调度程序(您没有),否则无法确定性地测试竞争条件。您可以做的最接近的事情是在getInstance()方法中添加可配置的延迟,或者在可能显示的问题中编写代码并在循环中运行数千次。

顺便说一下,这些都不构成“证明”。 Formal Verification会,但即使对于相对少量的代码也非常非常难。

答案 1 :(得分:13)

您可以强制ExpensiveObject在测试中花费很长时间吗?如果是这样,只需从两个不同的线程中调用getInstance()两次,在足够短的时间内第一个构造函数在第二次调用之前就不会完成。您将最终构建两个不同的实例,这是您应该失败的地方。

让天真的双重检查锁定失败会更难,请注意......(即使没有为变量指定volatile也不安全。)

答案 2 :(得分:5)

这不是使用代码,但这是我如何证明它的一个例子。我忘记了这样的执行图的标准格式,但其含义应该足够明显。

| Thread 1              | Thread 2              |
|-----------------------|-----------------------|
| **start**             |                       |
| getInstance()         |                       |
| if(instance == null)  |                       |
| new ExpensiveObject() |                       |
| **context switch ->** | **start**             |
|                       | getInstance()         |
|                       | if(instance == null)  | //instance hasn't been assigned, so this check doesn't do what you want
|                       | new ExpensiveObject() |
| **start**             | **<- context switch** |
| instance = result     |                       |
| **context switch ->** | **start**             |
|                       | instance = result     |
|                       | return instance       |
| **start**             | **<- context switch** |
| return instance       |                       |

答案 3 :(得分:3)

由于这是Java,您可以使用thread-weaver库在代码中注入暂停或中断并控制多个执行线程。通过这种方式,您可以获得一个缓慢的ExpensiveObject构造函数而无需修改构造函数代码,就像其他人(正确地)建议的那样。

答案 4 :(得分:2)

嗯......这段代码的结果将是假的,你期望是真的。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class LazyInitRace {

    public class ExpensiveObject {
        public ExpensiveObject() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }

    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }

    public static void main(String[] args) {
        final LazyInitRace lazyInitRace = new LazyInitRace();

        FutureTask<ExpensiveObject> target1 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target1).start();

        FutureTask<ExpensiveObject> target2 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target2).start();

        try {
            System.out.println(target1.get() == target2.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }
}

答案 5 :(得分:0)

在构造函数中加入一个非常长的计算:

public ExpensiveObject()
{
    for(double i = 0.0; i < Double.MAX_VALUE; ++i)
    {
        Math.pow(2.0,i);
    }
}

如果Double.MAX_VALUE/2.0花费的时间过长,您可能希望将终止条件降低到MAX_VALUE或除以更大的数字。

答案 6 :(得分:0)

您可以使用调试器轻松证明这一点。

  1. 编写一个调用的程序 getInstance()分开两个 线程。
  2. 在构造上设置断点 ExpensiveObject的。确保 调试器只会暂停 线程,而不是VM。
  3. 然后当第一个线程停止时 断点,让它暂停。
  4. 当第二个线程停止时,您只需继续。
  5. 如果检查结果 两个线程的getInstance()调用, 他们将指的是不同的 实例
  6. 这样做的好处是你实际上并不需要ExpensiveObject,任何Object实际上都会产生相同的结果。您只是使用调试器来安排执行特定代码行,从而创建确定性结果。

答案 7 :(得分:-1)

嗯,这不是线程安全的。 线程安全性的证明是随机的,但相当简单:

  1. 使ExpensiveObject构造函数完全安全:

    synchronized ExpensiveObject(){...

  2. 放置构造函数代码,检查是否存在另一个对象副本 - 然后引发异常。

  3. 创建线程安全方法以清除'instance'变量

  4. 将getInstance / clearInstance的顺序代码循环以供多个线程执行并等待(2)

  5. 的异常