测试方法是否以同步方式运行
test if a method behaves in a synchronized way
下面的代码是概述此单元测试 objective 的片段。下面演示了 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);
}
}
编辑:
现在发生了什么:线程被创建,文件被创建,文件被删除,线程终止,断言不证明什么并重复
预期结果:线程应该全部启动并且断言应该确保一次只创建一个文件并且其他线程正在等待,而不是执行方法
双重编辑:
我真正需要的是对上面的单元测试进行重构,以便它完成它应该做的事情(如上所述)
创建 File
subclass 覆盖 createNewFile
方法,如下所示:
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();
}
}
将此 class 的实例传递给您的线程
最后断言您的测试 isSynchronizationFailed 为假。
如果两个线程以某种方式同时进入 createNewFile 方法,您会将 isSynchronizationFailed 变量设置为 true。
当然对于这个非常简单的用例来说,这很愚蠢,因为同步关键字就在那里。但一般来说,如果你想测试一个方法是否从未被并发调用,你可以这样抛出:
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 注入它?
下面的代码是概述此单元测试 objective 的片段。下面演示了 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);
}
}
编辑:
现在发生了什么:线程被创建,文件被创建,文件被删除,线程终止,断言不证明什么并重复
预期结果:线程应该全部启动并且断言应该确保一次只创建一个文件并且其他线程正在等待,而不是执行方法
双重编辑:
我真正需要的是对上面的单元测试进行重构,以便它完成它应该做的事情(如上所述)
创建 File
subclass 覆盖 createNewFile
方法,如下所示:
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();
}
}
将此 class 的实例传递给您的线程
最后断言您的测试 isSynchronizationFailed 为假。
如果两个线程以某种方式同时进入 createNewFile 方法,您会将 isSynchronizationFailed 变量设置为 true。
当然对于这个非常简单的用例来说,这很愚蠢,因为同步关键字就在那里。但一般来说,如果你想测试一个方法是否从未被并发调用,你可以这样抛出:
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 注入它?