简体   繁体   English

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

[英]Proving the following code not thread safe

How can I quickly prove that the following class is not thread-safe (as it uses Lazy Initialization and not using synchronization) by writing some code ? 如何通过编写一些代码来快速证明以下类不是线程安全的(因为它使用惰性初始化而不使用同步)? In other words, if I am testing the following class for thread safety, how can I fail it? 换句话说,如果我正在测试以下类的线程安全性,我怎么能失败呢?

public class LazyInitRace {
  private ExpensiveObject instance = null;

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

By definition, race conditions cannot be tested deterministically, unless you control the thread scheduler (which you don't). 根据定义,除非您控制线程调度程序(您没有),否则无法确定性地测试竞争条件。 The closest thing you can do is either to add a configurable delay in the getInstance() method, or write code where the problem might manifest and run it thousands of times in a loop. 您可以做的最接近的事情是在getInstance()方法中添加可配置的延迟,或者编写问题可能显示的代码并在循环中运行数千次。

BTW, none of this really constitutes "proof". 顺便说一句,这些都不构成“证明”。 Formal Verification would, but is very, very hard to do, even for relatively small amounts of code. 即使对于相对少量的代码, 正式验证也会非常非常困难。

Can you force ExpensiveObject to take a long time to construct within your test? 你可以强迫ExpensiveObject在你的测试中花费很长时间吗? If so, just call getInstance() twice from two different threads, in a short enough time that the first constructor won't have completed before the second call is made. 如果是这样,只需从两个不同的线程调用两次getInstance() ,在足够短的时间内第一个构造函数在第二次调用之前就不会完成。 You will end up with two different instances being constructed, which is where you should fail. 您将最终构建两个不同的实例,这是您应该失败的地方。

Making naive double-checked locking fail will be harder, mind you... (even though it's not safe without specifying volatile for the variable). 天真的双重检查锁定失败将会更加困难,请注意......(即使没有为变量指定volatile也不安全)。

This isn't using code, but here's an example of how I'd prove it. 这不是使用代码,但这是我如何证明它的一个例子。 I forget the standard format for execution diagrams like this, but the meaning should be obvious enough. 我忘记了这样的执行图的标准格式,但其含义应该足够明显。

| 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       |                       |

Since this is Java, you can use the thread-weaver library to inject pauses or breaks into your code and control multiple threads of execution. 由于这是Java,因此您可以使用thread-weaver库在代码中注入暂停或中断并控制多个执行线程。 This way you can get a slow ExpensiveObject constructor without having to modify the constructor code, as other have (correctly) suggested. 这样你就可以获得一个缓慢的ExpensiveObject构造函数,而不必修改构造函数代码,正如其他人(正确)建议的那样。

Well... The result of this code will be false, where you expect for a true. 嗯...这段代码的结果将是假的,你期望的是真的。

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) {
        }
    }
}

Put a really long calculation in the constructor: 在构造函数中进行一个非常长的计算:

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

You might want to decrease the termination condition to Double.MAX_VALUE/2.0 or divide by a larger number if MAX_VALUE is taking too long for your liking. 如果MAX_VALUE花费太长时间,您可能希望将终止条件降低到Double.MAX_VALUE/2.0或除以更大的数字。

You can prove it easily with the debugger. 您可以使用调试器轻松证明它。

  1. Write a program that calls getInstance() on two separate threads. 编写一个在两个独立线程上调用getInstance()的程序。
  2. Set a breakpoint on the construction of the ExpensiveObject. 在ExpensiveObject的构造上设置断点。 Make sure the debugger will only suspend the thread, not the VM. 确保调试器只挂起线程,而不是VM。
  3. Then when the first thread stops on the breakpoint, leave it suspended. 然后当第一个线程在断点处停止时,将其暂停。
  4. When the second thread stops, you simply continue. 当第二个线程停止时,您只需继续。
  5. If you check the the result of the getInstance() call for both threads, they will be referring to different instances. 如果检查两个线程的getInstance()调用的结果,它们将引用不同的实例。

The advantage of doing it this way, is that you don't actually need an ExpensiveObject, any Object will in fact produce the same results. 这样做的好处是你实际上并不需要ExpensiveObject,任何Object实际上都会产生相同的结果。 You are simply using the debugger to schedule the execution of that particular line of code and thus creating a deterministic result. 您只是使用调试器来安排执行特定代码行,从而创建确定性结果。

Well, it is not thread safe. 好吧,它不是线程安全的。 Proofing of thread safety is random but rather simple: 线程安全性的证明是随机的,但相当简单:

  1. Make ExpensiveObject constructor fully safe: 使ExpensiveObject构造函数完全安全:

    synchronized ExpensiveObject(){ ... synchronized ExpensiveObject(){...

  2. Place to constructor code that checks if another copy of object exists - then raise an exception. 放置构造函数代码,检查是否存在另一个对象副本 - 然后引发异常。

  3. Create thread safe method to clear 'instance' variable 创建线程安全方法以清除'instance'变量

  4. Place sequential code of getInstance/clearInstance to loop for execution by multiple threads and wait exception from (2) 将getInstance / clearInstance的顺序代码放入循环以供多个线程执行并等待(2)异常

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM