【问题标题】:test if a method behaves in a synchronized way测试方法是否以同步方式运行
【发布时间】:2017-03-01 16:06:00
【问题描述】:

以下代码是概述此单元测试目标的 sn-p。下面演示了createFile 仅执行一项已知为线程安全操作的任务。

因此,这个想法更多的是围绕测试而不是实际操作;为了毫无疑问地证明,线程安全方法的行为,因为它的行为方式我们已经在历史上证明过。

public static final synchronized void createFile(final File file) throws IOException {
    file.createNewFile();
}

@Test
public void testCreateFileThreadSafety() throws Exception {
    for (int i = 1; i < 50; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    createFile(new File(i+".txt"));
                    new File(i+".txt").delete();
                } catch (IOException ex) {
                   System.out.println(ex.getMessage());
                }
            }
        }).start();
        assertTrue(file.getParentFile().listFiles().length == 0);
    }
}

编辑:

现在发生了什么:线程被创建,文件被创建,文件被删除,线程死亡,断言证明什么都没有并重复

预期结果:线程应该全部启动,并且断言应该确保一次只创建一个文件,并且其他线程正在等待,而不是执行该方法

双重编辑:

我真正需要的是对上述单元测试进行重构,以便它完成它应该做的事情(如上所述)

【问题讨论】:

  • 我不太了解这个策略。为什么要检查当前目录是否为空,每次启动线程创建然后删除文件时,测试您的方法是否已同步?以及为什么要同步此方法,因为 createNewFile() 已经自动创建了一个新的空文件...(引用自 javadoc)
  • 不详细说明为什么这不起作用;但指出概念上的弱点: - 没有必要“证明”synchronized 关键字有效。如:您也没有测试每个原始 int 值都可以容纳 32 位;并且没有一个只支持 31 位。因为这些都是您的应用程序范围之外的所有元素。换句话说:你可以/应该相信这样的事情。甚至当 JVM 中存在错误时:期望它是如此微妙,以至于您可以轻易想到的没有任何东西会发现此类问题。
  • -“真正的”线程安全是关于防止死锁或导致数据损坏或不一致的竞争条件。在一定程度上可以测试的;但是测试设置需要根据您的“被测代码”的具体情况进行定制。
  • 如果您的测试目标是测试发布的代码以外的其他内容,那么请展示您真正打算测试的内容。事实上,这个测试没有意义,也没有办法检查方法是否同步,因为如果它没有同步它会做完全相同的事情(因为 createNewFile 已经是线程安全的)。
  • 投票重新开放。我认为您无法通过 JUnit 测试来证明它,但您可以通过在方法中添加一些代码来证明它,例如增加一个 volatile 静态计数器,并检查它一次只增加 1。

标签: java multithreading junit synchronized


【解决方案1】:

当然,对于这个非常简单的用例来说,这很愚蠢,因为 synchronized 关键字就在那里。但一般来说,如果你想测试一个方法是否从不并发调用,你可以抛出这个:

static AtomicInteger c = new AtomicInteger();

public void knownNonThreadSafeMethod(final File file) throws IOException {
    int t = c.incrementAndGet();
    doSomething();   
    Thread.yield(); //try to force race conditions, remove in prod
    assert t == c.intValue();
}

如果您使用简单的 int i.s.o. AtomicInteger,编译器优化将删除断言。

static int c = 0;

public void knownNonThreadSafeMethod(final File file) throws IOException {
    int t = ++c;
    doSomething();   
    assert t == c; //smart-ass compiler will optimize to 'true'
}

使用 AtomicInteger,可以保证该值在所有 CPU 和所有线程上同步,因此您将检测到任何并发访问。

我知道它不在 JUnit 测试中,但我找不到任何非侵入性的方法来解决这个问题。也许你可以通过 AspectJ 注入它?

【讨论】:

    【解决方案2】:

    创建覆盖createNewFile 方法的File 子类,如下所示:

    class TestFile extends File {
    
        private final AtomicBoolean isCreated = new AtomicBoolean(false);
        private boolean isSynchronizationFailed = false;
    
        public boolean createNewFile() throws IOException {
            if (isCreated.compareAndSet(false, true)) {
                // give other threads chance to get here
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
                // cleanup
                isCreated.set(false);
            } else {
                isSynchronizationFailed = true;
            }
            return super.createNewFile();
        }
    }
    

    将此类的实例传递给您的线程

    最后断言你的测试 isSynchronizationFailed 是假的。

    如果两个线程同时进入 createNewFile 方法,你会将 isSynchronizationFailed 变量设置为 true。

    【讨论】:

    • 这是概念证明,你的 cmets 是有用的补充
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-31
    • 2021-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-03
    相关资源
    最近更新 更多