Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/380.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
单元测试Java中单例类的线程安全性?_Java_Junit_Thread Safety - Fatal编程技术网

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

单元测试Java中单例类的线程安全性?,java,junit,thread-safety,Java,Junit,Thread Safety,假设我有以下java类: static class Singleton { static Singleton i; static Singleton getInstance() { if (i == null) { i = new Singleton(); } return i; } } 现在,我们都知道这是可行的,但是-它显然不是线程安全的-我实际上并没有试图修复线程安全性-这更像是一个演示,我

假设我有以下java类:

static class Singleton {
    static Singleton i;

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

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

好吧,竞争条件本质上是概率的,因此没有确定的方法来真正生成竞争条件。任何针对当前代码的可能方法都需要多次运行,直到达到预期的结果。不过,您可以通过制作一个模拟的单例来测试,以模拟某个条件可能是什么样子,从而在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,就好像它们在“偶数线程id能够检查和初始化i”之前达到了“返回i”(有点,不完全是这样,但它模拟了行为)。尽管如此,在这种情况下偶数线程之间仍然存在竞争,因为偶数线程可能在另一个线程读取null后仍会写入i。为了提高性能,您需要实现线程安全性,以强制一个线程读取i,获取null,而另一个线程将i设置为new Singleton(),这是一个线程不安全的条件。但在这一点上,您最好只解决底层问题(只需使getInstance线程安全!)

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

此代码对我有效。 诀窍在于,它就像其他用户所说的那样具有概率性。 因此,应该采取的方法是多次运行

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);
        }
    }
}
公共类单线程安全{
公共静态final int并发线程=4;
私有无效单(){
//为单例分配一个数组
最终单例[]单例=新单例[并发线程];
//剩余线程数
最终AtomicInteger计数=新的AtomicInteger(并发线程);
//创建线程
对于(int i=0;i=1){
试试{Thread.sleep(10);}catch(InterruptedException ex){}
}

对于(inti=0;i在某些情况下,这种解决方案是有效的。不幸的是,很难测试单例来引发线程不安全

@Test
public void checkThreadUnSafeSingleton() throws InterruptedException {
    int threadsAmount = 500;
    Set<Singleton> singletonSet = Collections.newSetFromMap(new ConcurrentHashMap<>());

    ExecutorService executorService = Executors.newFixedThreadPool(threadsAmount);

    for (int i = 0; i < threadsAmount; i++) {
        executorService.execute(() -> {
            Singleton singleton = Singleton.getInstance();
            singletonSet.add(singleton);
        });
    }

    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.MINUTES);

    Assert.assertEquals(2, singletonSet.size());
}
@测试
public void checkThreadUnSafeSingleton()引发InterruptedException{
int threadsAmount=500;
Set singletonSet=Collections.newSetFromMap(新的ConcurrentHashMap());
ExecutorService ExecutorService=Executors.newFixedThreadPool(threadsAmount);
对于(int i=0;i{
Singleton Singleton=Singleton.getInstance();
singletonSet.add(singleton);
});
}
executorService.shutdown();
执行人服务。等待终止(1,时间单位。分钟);
Assert.assertEquals(2,singletonSet.size());
}

您试图测试它的哪一方面?如果它是“只创建一个实例”,那么单元测试就不能真正做到这一点。您可以编写一个测试,启动一组线程,并让每个线程调用
getInstance()
,然后检查您返回的实例——但即使是这样,最多也只是概率性的,可能不会达到您在现实生活中遇到的相同条件(在CPU使用率、缓存线等方面)。当涉及到检查竞争条件时,单元测试通常是不够的。这是使多线程处理更加困难的原因之一。可能很难编写引发不安全情况的测试,因为您必须以一种方式对两个线程计时,即两个线程同时进入if块,但一个线程在e other改变了它。问题是你不能以这种方式给线程计时,因为线程运行的时间和速度取决于大量的事情。@yshavit-是的,这就是为什么我要问这个问题的原因-我对此有很多想法,但我想不出任何确定性的东西!我会说写一个检查潜在不安全情况的测试需要对可能出现的错误有很好的理解,这样你才能尝试挑起这些情况。然而,一旦你理解了这些情况,你通常能够“证明”为什么它们是危险的,它们可能会发生-在这种情况下,您不再需要测试,但应该能够立即解决问题。创建并启动两个线程,在调用
getInstance()
之前等待倒计时闩锁;然后倒计时闩锁。您可能需要多次运行它(在其间重置i),但这可能是最简单的竞赛方式。不幸的是,为了实现这一点而修改类并不是一个真正的选项-我们正在测试当前规范中的错误。有一些解决方案可以公开用于单元测试的私有字段。除了
instance
字段必须在测试环境中可公开和设置。最简单的方法是使用“反射”来设置
实例
字段(
i
)每次迭代后归零。小提示,但你知道CountDownLatch吗?你基本上是用AtomicInteger来代替它。@yshavit这只是为了简单起见。@user9335240由你决定,但我不认为两次尝试睡眠和一个循环比
latch.await()
:)简单