单元测试Java中单例类的线程安全性?

时间:2018-02-13 17:08:11

标签: java junit thread-safety

让我们想象我有以下java类:

static class Singleton {
    static Singleton i;

    static Singleton getInstance() {
        if (i == null) {
            i = new Singleton();
        }
        return i;
    }
}

现在,我们都知道这会起作用,但是 - 它显然不是线程安全的 - 我实际上并没有尝试修复线程安全 - 这更像是一个演示,我的另一个类是相同的,但是使用了一个互斥体和同步 - 将针对每个运行单元测试以显示一个是线程安全的,而另一个不是。如果getInstance不是线程安全的,那么单元测试会失败的是什么?

3 个答案:

答案 0 :(得分:0)

嗯,竞争条件本质上是概率性的,所以没有确定的方法来真正产生竞争条件。任何针对当前代码的可能方法都需要多次运行,直到达到预期的结果。您可以通过使模拟单例进行测试来强制执行对i的访问的松散排序,以模拟某个条件可能看起来像什么。具有同步性的经验法则是预防措施,试图测试并找出代码库中错误代码被破坏后的错误。

static class Singleton {
    static Singleton i;

    static Singleton getInstance(int tid) {
        if (i == null) {

            if (tid % 2 == 0) i =  new Singleton()
        }
        return i;
    }
}

所以某些线程会写入i,而其他线程会读取i,就像它们已经到达"返回i"之前"偶数线程ID能够检查并初始化i" (有点,不完全是,但它模拟了行为)。尽管如此,在这种情况下,偶数线程之间存在竞争,因为偶数线程在另一个读取null之后仍然可以写入i。为了改进,你需要实现线程安全来强制一个线程读取i的条件,得到null,而另一个线程将i设置为新的Singleton()一个线程不安全的条件。但是在那时你最好只解决潜在的问题(只需使getInstance线程安全!)

TLDR:在不安全的函数调用中可能会出现无限多的竞争条件。您可以模拟代码以生成特定竞争条件的模拟(例如,仅在两个线程之间),但仅仅对竞争条件进行全面测试是不可行的#34;

答案 1 :(得分:0)

这段代码对我有用。 诀窍在于它是其他用户所说的概率。 因此,应该采取的方法是运行多次。

public class SingletonThreadSafety {

    public static final int CONCURRENT_THREADS = 4;

    private void single() {
        // Allocate an array for the singletons
        final Singleton[] singleton = new Singleton[CONCURRENT_THREADS];
        // Number of threads remaining
        final AtomicInteger count = new AtomicInteger(CONCURRENT_THREADS);
        // Create the threads
        for(int i=0;i<CONCURRENT_THREADS;i++) {
            final int l = i; // Capture this value to enter the inner thread class  
            new Thread() {
                public void run() {
                    singleton[l] = Singleton.getInstance();
                    count.decrementAndGet();
                }
            }.start();
        }
        // Ensure all threads are done
        // The sleep(10) is to be somewhat performant, (if just loop,
        //    this will be a lot slow. We could use some other threading
        //    classes better, like CountdownLatch or something.)
        try { Thread.sleep(10); } catch(InterruptedException ex) { }
        while(count.get() >= 1) {
            try { Thread.sleep(10); } catch(InterruptedException ex) { }
        }
        for(    int i=0;i<CONCURRENT_THREADS - 1;i++) {
            assertTrue(singleton[i] == singleton[i + 1]);
        }

    }

    @Test
    public void test() {
        for(int i=0;i<1000;i++) {
            Singleton.i = null;
            single();
            System.out.println(i);
        }
    }
}

这必须在Singleton设计模式中做出一些改变。现在可以在Test类中访问实例变量。这样我们可以在每次重复测试时再次重置null可用的Singleton实例,然后我们重复测试1000次(如果你有更多的时间,你可以做得更多,有时会发现奇怪的线程问题需要这一点)。

答案 2 :(得分:0)

在某些情况下,此解决方案有效。不幸的是,很难测试单例来引发线程不安全。

while(fileReader.hasNext())
{
   text = fileReader.nextLine();

   splitText = text.split(:);
}