Java: ссылка escape

17

Прочитайте, что следующий код является примером «небезопасной конструкции», поскольку позволяет этой ссылке сбежать. Я не мог понять, как «это» ускользает. Я довольно новичок в мире java. Может ли кто-нибудь помочь мне понять это.

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}
    
задан devnull 14.09.2010 в 02:49
источник

4 ответа

21

Пример, который вы опубликовали в своем вопросе, получен из «Java Concurrency In Practice» от Brian Goetz и др. , Это в разделе 3.2 «Публикация и побег». Я не буду пытаться воспроизвести детали этого раздела здесь. (Идите купите копию своей книжной полки или займите копию у своих сотрудников!)

Проблема, проиллюстрированная примером кода, заключается в том, что конструктор позволяет ссылаться на объект, который создается для «escape», прежде чем конструктор завершит создание объекта. Это проблема по двум причинам:

  1. Если ссылка исчезает, что-то может использовать объект, прежде чем его конструктор завершит инициализацию и увидит его в непоследовательном (частично инициализированном) состоянии. Даже если объект выходит после завершения инициализации, объявление подкласса может привести к его нарушению.

  2. Согласно JLS 17.5 , окончательные атрибуты объекта можно безопасно использовать без синхронизации. Однако это справедливо только в том случае, если ссылка на объект не опубликована (не исчезает) до завершения его конструктора. Если вы нарушите это правило, результатом будет коварная ошибка параллелизма, которая может укусить вас, когда код выполняется на многоядерных / многопроцессорных машинах.

Пример ThisEscape скрыт, потому что ссылка ускользает с помощью ссылки this , переданной неявно на анонимный конструктор класса EventListener . Однако те же проблемы возникнут, если ссылка будет опубликована слишком скоро.

Вот пример, иллюстрирующий проблему неполностью инициализированных объектов:

public class Thing {
    public Thing (Leaker leaker) {
        leaker.leak(this);
    }
}

public class NamedThing  extends Thing {
    private String name;

    public NamedThing (Leaker leaker, String name) {
        super(leaker);

    }

    public String getName() {
        return name; 
    }
}

Если метод Leaker.leak(...) вызывает getName() на просочившемся объекте, он получит null ... потому что в тот момент времени цепочка конструктора объекта не завершилась.

Вот пример, иллюстрирующий проблему небезопасной публикации для атрибутов final .

public class Unsafe {
    public final int foo = 42;
    public Unsafe(Unsafe[] leak) {
        leak[0] = this;   // Unsafe publication
        // Make the "window of vulnerability" large
        for (long l = 0; l < /* very large */ ; l++) {
            ...
        }
    }
}

public class Main {
    public static void main(String[] args) {
        final Unsafe[] leak = new Unsafe[1];
        new Thread(new Runnable() {
            public void run() {
                Thread.yield();   // (or sleep for a bit)
                new Unsafe(leak);
            }
        }).start();

        while (true) {
            if (leak[0] != null) {
                if (leak[0].foo == 42) {
                    System.err.println("OK");
                } else {
                    System.err.println("OUCH!");
                }
                System.exit(0);
            }
        }
    }
}

Некоторые прогоны этого приложения могут печатать "OUCH!" вместо «ОК», что указывает на то, что основной поток заметил объект Unsafe в «невозможном» состоянии из-за небезопасной публикации через массив leak . Это произойдет или не будет зависеть от вашей JVM и вашей аппаратной платформы.

Теперь этот пример явно искусственен, но нетрудно представить, как это может происходить в реальных многопоточных приложениях.     

ответ дан Stephen C 14.09.2010 в 03:49
источник
11

У меня были такие же сомнения.

Дело в том, что каждый класс, который получает экземпляр внутри другого класса, имеет ссылку на охватывающий класс в переменной $this .

Это то, что java вызывает синтетический , это не то, что вы определяете, чтобы быть там, но что-то java делает для вас автоматически.

Если вы хотите, чтобы это для себя положило точку останова в строке doSomething(e) и проверьте, какие свойства EventListener имеют.

    
ответ дан Pablo Fernandez 14.09.2010 в 02:59
источник
5

Я предполагаю, что метод doSomething объявлен в классе ThisEscape , и в этом случае ссылка, безусловно, может «убежать».
I.e., некоторое событие может вызвать этот EventListener сразу после его создания и до выполнения конструктора ThisEscape . А слушатель, в свою очередь, вызовет метод экземпляра ThisEscape .

Я немного изменю ваш пример. Теперь переменную var можно получить в doSomething до того, как она назначена в конструкторе.

public class ThisEscape {
    private final int var;

    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        );

        // more initialization
        // ...

        var = 10;
    }

    // result can be 0 or 10
    int doSomething(Event e) {
        return var;
    }
}
    
ответ дан Nikita Rybak 14.09.2010 в 02:55
источник
3

У меня был точно такой же вопрос, когда я читал « Java Concurrency In Practice » Брайана Гетца.

Ответ Стивена C (принятый) превосходный! Я только хотел добавить поверх этого еще одного ресурса, который я обнаружил. Это от JavaSpecialists , где д-р Хайнц М. Кабуц анализирует именно пример кода, который отправлен devnull . Он объясняет, какие классы генерируются (внешние, внутренние) после компиляции и как this уходит. Я нашел это объяснение полезным, поэтому мне захотелось поделиться :)

issue192 (где он расширяет пример и обеспечивает условие гонки).

issue192b (где он объясняет, какие классы генерируются после компиляции и как выполняется this .)     

ответ дан alexandros 26.11.2014 в 00:55
источник