Java: ссылка escape

19

Прочитайте, что следующий код является примером «небезопасной конструкции», поскольку позволяет этой ссылке сбежать. Я не мог понять, как «это» ускользает. Я довольно новичок в мире 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
источник
  • Не могли бы вы быть более ясными? что такое отношение «случится раньше». Или, может быть, представить пример, где воздействие действительно происходит? –  Pablo Fernandez 14.09.2010 в 04:49
  • @Pablo - исправьте свой ответ, и я удалю downvote :-) –  Stephen C 14.09.2010 в 05:07
  • @Gabe - не читая его подробно, у меня нет оснований полагать, что любой из них устарел. В этой статье не говорится, что в модели памяти есть «ошибка» ... если это то, что вы подразумеваете. –  Stephen C 14.09.2010 в 05:44
  • Стивен: Гетц говорит: «JMM пересматривается в рамках Java Community Process JSR 133, который, в частности, изменит семантику изменчивости и окончательности, чтобы привести их в соответствие с общей интуицией». Это было написано в 2002 году. Сколько изменилось за последние 8 лет? –  Gabe 14.09.2010 в 05:58
  • @Gabe. Модель памяти изменилась с JLS 2 на JLS 3 в результате JSR 133. Чтобы увидеть различия, сравните главу 17 второго и третьего выпусков JLS. AFAIK, ни одно из изменений недействительно в статье Goetz за 2002 год. Его книга была пересмотрена в 2006 году и описывает модель памяти JLS 3rd edition. Модель памяти не изменилась с момента выпуска JLS 3rd Edition / Java 5.0. –  Stephen C 14.09.2010 в 06:32
Показать остальные комментарии
12

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

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

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

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

    
ответ дан Pablo Fernandez 14.09.2010 в 02:59
источник
  • см. мой ответ для настоящего объяснения. –  Stephen C 14.09.2010 в 04:01
  • Я думаю, что это важная часть проблемы; он объясняет, как именно это ускользает, что задается вопросом (почему экранирование плохое, не является явной частью вопроса). –  erickson 14.09.2010 в 08:21
  • Я удивлен, что этот ответ получил всего 8 голосов! –  Karthick 23.10.2016 в 04:30
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
источник
  • Это не самый счастливый пример, поскольку книга, в которой эта проблема возникает (Concurrency in Practice), говорит, что даже если registerListener является последней строкой в ​​конструкторе, плохо сконструированный объект все еще может убежать –  Pablo Fernandez 14.09.2010 в 03:01
  • @Pablo Как? (Я не оспариваю книгу, просто интересуюсь «неудачным» примером) –  Nikita Rybak 14.09.2010 в 03:03
  • Я тоже думаю, что это имеет какое-то отношение к тому факту, что внешний объект видим в методе onEvent. И java не гарантирует правильную инициализацию переменных, если поля не объявлены окончательными (это также из этой книги). Примечание: я не закончил читать: P –  Pablo Fernandez 14.09.2010 в 03:14
  • Надеюсь, что Джон Скит вскочит и вскоре откроет тайну :) (и там тоже наши upvotes) –  Pablo Fernandez 14.09.2010 в 03:15
  • @Pablo Ну, не забудьте разместить здесь, если вы найдете объяснение в книге :) В конце концов, это не Библия, мы не должны просто верить в каждое написанное заявление. –  Nikita Rybak 14.09.2010 в 03:50
Показать остальные комментарии
3

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

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

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

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

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