Нарушение Параллельного Кода
Допустим, у меня есть следующий класс:
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();
}
}
Легко видеть, что при многопоточном выполнении приведенного выше кода мы могли бы получить NullPointerExeption
в somethingElse
. Например, может быть, что failField != null
и до возвращения failField.length()
destroy
вызывается поэтому делание failField
на null
.
Я хочу создать многопоточную программу, которая будет способна "выбрасывать" NullPointerException
при использовании BuggyClass
. Я знаю, что поскольку программа многопоточна, может быть, этого никогда не произойдет, но я думаю, что должен быть какой-то лучший тест, который увеличивает вероятность получения исключения. Так ведь?
Я попробовал следующее:
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_INV
и NUM_OF_THREADS
, но никогда не удавалось получить NullPointerException
.
Есть идеи о том, как я могу создать тест, которыйувеличивает мои шансы получить исключение без изменения BuggyClass
?
4 ответа:
Это не удается... по крайней мере, на моей машине. Дело в том, что Бегущий проглатывает исключение. Попробуйте вместо этого:
executor.submit(new Runnable() { public void run() { for (int i = 0; i < NUM_OF_INV; i++) { try { bc.somethingElse(); } catch (NullPointerException e) { e.printStackTrace(); } } } });
Я получаю NPE каждый раз, когда я запускаю его.
Хотя в вашем коде есть гонка данных, может быть невозможно увидеть какие-либо проблемы, вызванные этой гонкой данных. Скорее всего, JIT-компилятор преобразует метод somethingElse в нечто подобное:
public long somethingElse() { String reg = failField; // load failField into a CPU register if (reg == null) { return -1; } return reg.length(); }
Это означает, что компилятор не будет загружать ссылку failField после выполнения условия. И невозможно вызватьNullPointerException .
Update: я скомпилировал Метод
somethingElse
с GCJ, чтобы увидеть некоторые реальные и оптимизированные выходные данные ассемблера. Выглядит это следующим образом:Из этого кода видно, что Ссылка failField загружается один раз. Конечно, нет никакой гарантии, что все реализации будут использовать одну и ту же оптимизацию сейчас и навсегда. Так что не стоит на это полагаться.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.length()
, а также сразу послеfailField = null
в методеdestroy()
. Это расширило бы окно для методаsomethingElse()
, чтобы получить доступ к переменной в состоянииnull
.
Я удивлен, что никто не заметил тот факт, что "failedField" не был префиксом с ключевым словом volatile.
Хотя действительно существует возможность возникновения гонки в методе create (), причина, по которой он, вероятно, работает на машинах других людей, заключается в том, что "failedField" не был в общей памяти и вместо него использовалось кэшированное значение "failedField".
Кроме того, ссылки на 64-битные машины не так надежны, как вы думаете. Вот почему атомарность существует на Яве.утиль.параллельный