Как смоделировать условия гонки конструктора?
Я читаю "Параллелизм Java на практике" и смотрю пример кода на странице 51.
Это говорит о том, что если поток имеет ссылки на общий объект, то другие потоки могут иметь доступ к этому объекту до того, как конструктор завершит выполнение.
Я попытался применить это на практике и поэтому написал этот код, думая, что, если я запустил его достаточно раз, возникнет исключение RuntimeException("World is f*cked"). Но это не так.
Это тот случай, когда спецификация Java не гарантирует что-то, а моя конкретная реализация Java гарантирует это для меня? (версия Java: 1.5.0 в Ubuntu) Или я что-то неправильно прочитал в книге?
Код: (я ожидаю исключения, но оно никогда не выбрасывается)
public class Threads {
private Widgit w;
public static void main(String[] s) throws Exception {
while(true){
Threads t = new Threads();
t.runThreads();
}
}
private void runThreads() throws Exception{
new Checker().start();
w = new Widgit((int)(Math.random() * 100) + 1);
}
private class Checker extends Thread{
private static final int LOOP_TIMES = 1000;
public void run() {
int count = 0;
for(int i = 0; i < LOOP_TIMES; i++){
try {
w.checkMe();
count++;
} catch(NullPointerException npe){
//ignore
}
}
System.out.println("checked: "+count+" times out of "+LOOP_TIMES);
}
}
private static class Widgit{
private int n;
private int n2;
Widgit(int n) throws InterruptedException{
this.n = n;
Thread.sleep(2);
this.n2 = n;
}
void checkMe(){
if (n != n2) {
throw new RuntimeException("World is f*cked");
}
}
}
}
4 ответа
Ну, вам нужно понять проблемы немного больше. На самом деле это не тот случай, когда что-то было или не было "гарантировано". В случае проблем с параллелизмом, ничто не может быть гарантировано, если вы действительно не сделаете определенные вещи, чтобы заставить проблему возникнуть Вы просто полагаетесь на надежду, что должно произойти достаточное количество пробежек, а это не так. Подобные проблемы трудно предсказать, поэтому параллелизм - сложная проблема. Вы можете попытаться выполнить больше работы над своими функциями, но я вас уверяю, это реальные проблемы, от которых среда выполнения не спасет вас.
Вы не публикуете ссылку, пока конструктор не завершит работу, измените Widgit
как это:
private class Widgit{ // NOTE: Not class is not static anymore
private int n;
private int n2;
Widgit(int n) throws InterruptedException{
this.n = n;
w = this; // publish reference
Thread.sleep(2);
this.n2 = n;
}
void checkMe(){
if (n != n2) {
throw new RuntimeException("World is f*cked");
}
}
Должен теперь бросить.
Изменить: Вы должны также объявить Widgit
поле как volatile
:
private volatile Widgit w;
Перед сном запустите новый поток, который печатает значение n2. Вы увидите, что второй поток может получить доступ к объекту до завершения конструктора.
Следующий пример демонстрирует это на Sun JVM.
/* The following prints
Incomplete initialisation of A{n=1, n2=0}
After initialisation A{n=1, n2=2}
*/
public class A {
final int n;
final int n2;
public A() throws InterruptedException {
n = 1;
new Thread(new Runnable() {
public void run() {
System.out.println("Incomplete initialisation of " + A.this);
}
}).start();
Thread.sleep(200);
this.n2 = 2;
}
@Override
public String toString() {
return "A{" + "n=" + n + ", n2=" + n2 + '}';
}
public static void main(String... args) throws InterruptedException {
System.out.println("After initialisation " + new A());
}
}
Это никогда не бросит RunTimeException
потому что ваш Widgit
переменная экземпляра w
остается нулевым, пока код конструктора не будет выполнен. Пока ваш главный поток спит в Widgit
конструктор, ваш Checker
экземпляр поражает NullPointerException
постоянно как w
переменная по-прежнему равна нулю. Когда ваш основной поток завершает строительство, два int
переменные в виджите равны.