如何从多个线程测试类的线程安全性



我正在处理线程安全的单例类,我知道下面的代码不是线程安全的,因为我正在进行双重检查锁定错误。

protected static TestSingleton instance;
private TestSingleton() {
    // some code
}
public static synchronized void setup() {
    if (instance == null) {
        TestSingleton holder = new TestSingleton();
        instance = holder;
    }
}
public static TestSingleton getInstance() {
    if (instance == null) {
        setup();
    }
    return instance;
}

但我正在尝试测试它,这样我就可以通过编程证明它不是线程安全的类。这是为了我的学习经验。

我如何通过编程测试它并证明它不是线程安全的?

归根结底:您的程序有一定数量的线程,在一定数量的处理器上运行,但您的计算机只有一个主内存。

每个线程将以某种顺序读取和写入不同的内存位置,这在一定程度上与其他线程的操作无关;所有这些读取和写入都必须在它们进入内存时进行序列化。也就是说,存储器系统必须一个接一个地执行这些读取和写入。

对于多线程程序的任何给定执行,可以有无数不同的方式序列化不同线程的操作。从字面上看,太多了,数不过来。如果哪怕是其中一个可能的序列化导致您的程序产生错误的输出,那么您的程序就不是线程安全的。

那么,如何测试它们呢?

你不能。它们太多了,可能在一台计算机(例如,您客户的任务关键型服务器)上频繁发生的串行化可能在其他计算机(例如您的测试线束)上永远不会发生

确保线程安全的唯一方法是使用经过数学验证的算法来同步线程的操作,这样就不会发生糟糕的交错。

简短回答:你不能

无论您在测试环境中运行此代码多少次,它实际正常工作的概率都为非零。问题是,你无法真正估计这种概率,因此无法找到显示正确结果的概率接近1所需的测试运行次数。

但是您实际上可以推理给定程序是如何执行的。找到一种让多线程程序中断的方法通常比证明它永远不会中断更容易。

在这种情况下,存在以下问题:在线程1中执行setup期间,对instance写入与从instance执行读取以返回线程2中执行getInstance的值不存在先发生后发生的关系,也与从instance执行来检查线程2中setup执行中是否为空不存在先发关系。这意味着来自不同线程的两个调用可能会产生不同的结果,也就是说,实现已经中断,我们甚至没有开始考虑代码重新排序。

例如,通过将instance声明为易失性来建立这样的关系将修复它

在这篇由Alexey Shipilev撰写的关于Java内存模型的优秀论文中,您可以了解更多关于顺序一致性、程序顺序和先发生后发生关系的信息。这将对你大有帮助。

不幸的是,上述推理的自动化目前是不可能的。

我已经了解了为什么我下面的响应不正确。请参阅相关的评论线程,了解原因。

我认为不可能表明你的程序在其当前化身中不是线程安全的,因为方法内部的逻辑足够"安全"。

这是因为,即使可能会尝试同时调用getInstance()内部的setup()调用,由于方法被声明为synchronized,setup(。

假设您有两个线程,A和B,竞争对setup()的访问。A首先执行,并在实例变量中设置对TestSingleton对象的引用。当A在setup()方法内部时,B试图调用它,但被阻止了。A完成,B进入方法。由于instance现在不为null,所以setup()中的任何逻辑都不会被B调用。因此,您永远不会在setup(()中看到条件块内部多次调用该逻辑

通过您所描述的测试,您最有可能找到的是两个线程从其getInstance()方法中获得TestSingleton的不同实例,或者一个线程从该方法中获得了null返回值,或一个线程在实例完全初始化之前返回实例。

问题是,你无法确定这些情况发生的可能性有多大,事实上,它们可能非常不可能发生。您可能会在没有看到任何失败行为的情况下运行大量的测试迭代。如果确实看到其中一个,那么您将证明该程序不是线程安全的,但没有看到任何一个就证明不了什么。

线程安全性是一种程序属性,必须通过对程序的分析来证明它。它无法通过有限时间内的测试来证明。

最新更新