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

Don*_* Ch 12 java multithreading thread-safety

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

public class LazyInitRace {
  private ExpensiveObject instance = null;

  public ExpensiveObject getInstance() {
     if (instance == null)
        instance = new ExpensiveObject();
    return instance;
  }
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*rdt 15

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

顺便说一句,这些都不构成"证明".即使对于相对少量的代码,正式验证也会非常非常困难.

  • 任何它失败的演示*都证明它可能会失败,所以我在这里看到使用"证据"并没有任何问题. (3认同)
  • 同意.反例证明是有效的证明. (2认同)

Jon*_*eet 13

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

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


Her*_*rms 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       |                       |
Run Code Online (Sandbox Code Playgroud)


nan*_*nda 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) {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)