java - 破解并发代码

标签 java multithreading concurrency synchronized executorservice

假设我有以下类(class):

public class BuggyClass {

    private String failField = null;

    public void create() {
        destroy();
        synchronized (this) {
            failField = new String("Ou! la la!");
        }
    }

    public void destroy() {
        synchronized (this) {
            failField = null;
        }
    }

    public long somethingElse() {
        if (failField == null) {
            return -1;
        }
        return failField.length();
    }

}

很容易看出,在以上代码的多线程执行中,我们可以在somethingElse 中得到一个NullPointerExeption。例如,它可能是 failField != null 并且在返回之前 failField.length() destroy 被调用因此使得 failFieldnull

我想创建一个多线程程序,在使用 BuggyClass 时能够“抛出”一个 NullPointerException。我知道,由于该程序是多线程的,所以这可能永远不会发生,但我想应该有一些更好的测试来增加出现异常的可能性。对吧?

我尝试了以下方法:

final BuggyClass bc = new BuggyClass();
final int NUM_OF_INV = 10000000;
int NUM_OF_THREADS = 5;
ExecutorService executor = Executors.newFixedThreadPool(3 * NUM_OF_THREADS);

for (int i = 0; i < (NUM_OF_THREADS); ++i) {
    executor.submit(new Runnable() {
        public void run() {
            for(int i = 0; i< NUM_OF_INV; i++){
                bc.create();
            }
        }
    });
}


for (int i = 0; i < (NUM_OF_THREADS); ++i) {
    executor.submit(new Runnable() {
        public void run() {
            for(int i = 0; i< NUM_OF_INV; i++){
                bc.destroy();
        }}
    });
}

for (int i = 0; i < (NUM_OF_THREADS); ++i) {
    executor.submit(new Runnable() {
        public void run() {
            for(int i = 0; i< NUM_OF_INV; i++){
                bc.somethingElse();
        }}
    });
}   
executor.shutdown(); executor.awaitTermination(1, TimeUnit.DAYS);  

我使用不同的 NUM_OF_INVNUM_OF_THREADS 多次执行上述代码(方法),但从未设法获得 NullPointerException

关于如何创建一个测试增加我在不更改 BuggyClass 的情况下获得异常的机会的任何想法?

最佳答案

虽然您的代码中存在数据竞争,但可能无法发现任何由此数据竞争 引起的问题。最有可能的是,JIT 编译器会将方法 somethingElse 转换成如下所示:

public long somethingElse() {
    String reg = failField; // load failField into a CPU register
    if (reg == null) {
        return -1;
    }
    return reg.length();
}

这意味着,编译器不会在条件之后加载引用failField。并且不可能触发NullPointerException


更新:我用 GCJ 编译了方法 somethingElse 以查看一些真实的和优化的汇编器输出。它看起来如下:

long long BuggyClass::somethingElse():
    movq    8(%rdi), %rdi
    testq   %rdi, %rdi
    je      .L14
    subq    $8, %rsp
    call    int java::lang::String::length()
    cltq
    addq    $8, %rsp
    ret
.L14:
    movq    $-1, %rax
    ret

您可以从这段代码中看到,引用failField 被加载了一次。当然,不能保证所有的实现现在和永远都使用相同的优化。所以,您不应该依赖它。

关于java - 破解并发代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23119711/

相关文章:

c++ - 使用原子指令实现 C++ 锁?

java - 避免返回 LIST 时可能出现的并发问题

java - Executors.newFixedThreadPool 如何停止同步方法上的所有 Activity 线程

c - C中pthread_join()的返回顺序

java - Jenkins:Jenkins 未运行 TestNG 测试

java - Sitebricks JSON 默认序列化器。为什么它返回 text/json 而不是 application/json

java - 根据 Callable 的某些属性选择 ThreadPool 线程

java - 当有多个用户同时访问页面时,是否需要在我的controller方法中添加synchronization关键字?

java - 在 Eclipse 中,文本超出页面范围

java - 从插件中扩展 Activity 的类获取值