не финальная нелокальная переменная внутри анонимного класса - PullRequest
1 голос
/ 14 ноября 2011

Насколько я понимаю, в Java нет истинных замыканий. Вы можете передать функцию, сопровождая их классом; однако он не только многословен, но и (из-за модели памяти Java) любые ссылки в анонимном классе на переменные, определенные в среде, в которой он был создан, передаются как copy . Язык побуждает нас помнить об этом, позволяя анонимным классам ссылаться только на final переменных.

Что приводит меня к этому фрагменту кода, который я нашел в Bloch's Effective Java :

import java.util.concurrent.*;
public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args)
                    throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

Во-первых, я ожидал, что компилятор будет жаловаться, потому что stopRequested не является окончательным, и я ссылаюсь на него внутри анонимного класса. Мой компилятор не жаловался.

Во-вторых, я ожидал, что программа будет зацикливаться вечно, поскольку Java не поддерживает замыкания, и если анонимный класс действительно ссылается на фактическую переменную stopRequested из среды, в которой он был создан (а не является простой копией) тогда кажется, что у нас есть закрытие здесь. Джошуа Блох также сказал, что программа навсегда зацикливается на его компьютере. Но моя работает около секунды и выходит.

Какую часть модели памяти я неправильно понимаю?

Ответы [ 6 ]

3 голосов
/ 14 ноября 2011

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

Только локальные переменные должны быть final для использования анонимными классами.*

1 голос
/ 14 ноября 2011

Что вы неправильно понимаете, так это то, что только локальные переменные передаются как копии во внутренний класс, потому что они существуют в стеке и, следовательно, исчезнут, когда вызов метода вернется.

Истинные замыкания "магическим образом" обеспечивают контекст для всех захваченных переменных, чтобы выжить. Но для нелокальных переменных это не обязательно; они существуют в куче как часть их объекта или класса, поэтому Java позволяет им быть неконечными и все еще использоваться во внутреннем классе.

То, что я думаю Пример кода Блоха должен демонстрировать совершенно другую вещь: разные потоки могут иметь локальные копии любой переменной (локальной, экземпляровой или статической) в кеше своего ЦП и изменять один Марки потоков могут быть невидимы для других потоков в течение сколь угодно длительного времени. Чтобы обеспечить синхронизацию локальных копий, изменение должно происходить либо в synchronized блоке / методе, либо переменная должна быть объявлена ​​volatile.

1 голос
/ 14 ноября 2011

Во-первых, я ожидал, что компилятор будет жаловаться, потому что stopRequested нефинал, и я ссылаюсь на него внутри анонимного класса. Мой компилятор не жаловался.

stopRequested является переменной static.

Во-вторых, я ожидал, что программа будет зацикливаться вечно, так как, в общем, Java не поддерживает замыкания и если анонимный класс действительно ссылаясь на фактическую переменную stopRequested из среды, в которой она был построен (а не простая копия), то кажется, что у нас есть закрытие здесь. Джошуа Блох также сказал, что программа зацикливается на его компьютер. Но моя работает около секунды и выходит

stopRequested не является переменной volatile. Поэтому он может работать вечно ( оптимизация поднятия флага . (Запуск в режиме -server)).

Поэтому приведенный ниже код

     while (!stopRequested)     
       i++;

можно изменить как

  boolean status = !stopRequested; 
  while(status)     
      i++;
1 голос
/ 14 ноября 2011

Это зацикливается на мне, и причина в том, что кеш процессора, а не анонимные методы.

При этом он всегда выходит:

private volatile static boolean stopRequested;
0 голосов
/ 14 ноября 2011

stopRequested не локальная переменная, это статическая переменная, поэтому она не должна быть конечной. Программа может зацикливаться вечно, потому что stopRequested не объявлен volatile и, следовательно, не гарантируется, что изменения в stopRequested, сделанные одним потоком, когда-либо будут видны в другом потоке. Если вы объявите stopRequested volatile, программа не будет работать вечно.

Ожидать, что компилятор пожалуется в этом примере, необычно. Обычно ожидается, что программа будет завершена вскоре после запуска. Блох показывает, что это может быть не так (что обычно удивляет читателя), а затем объясняет почему. Bloch - довольно сложное чтение, вы можете сначала попробовать другие книги по Java.

0 голосов
/ 14 ноября 2011

Вы можете получить доступ к полям через ссылку (неявно на OuterClass.this) или статическое поле по классу.Это только не конечные переменные, на которые вы не можете ссылаться.Примечание: если эта последняя ссылка указывает на что-то изменчивое, вы можете изменить его.

final int[] i = { 0 };
new Thread(new Runnable() {
    public void run() {
        i[0] = 1;
    }
}).start();
while(i[0] == 0);
System.out.println("i= " + i[0]);
...