Почему переменные, передаваемые в runnable, должны быть окончательными?

Если у меня есть, скажем, переменная int x = 1, и я объявляю исполняемый объект в основном потоке и хочу передать x методу исполняемого модуля run(), он должен быть объявлен final. Почему?

final int x = 0;//<----must be final...
private class myRun implements Runnable {

    @Override
    public void run() {
        x++;//
    }

}

person Community    schedule 11.07.2012    source источник
comment
Потому что так определяется язык. Предположительно, чтобы предотвратить изменение переменных в указанном методе в анонимном внутреннем классе. (Я считаю, что это также упрощает реализацию: только значения должны быть прокси-скопированы в анонимный тип, а исходные переменные больше не нужно хранить, как это требуется при полной семантике закрытия. .)   -  person    schedule 11.07.2012
comment
Если бы это было не так, ваши переменные могли бы быть изменены в любое время без предупреждения.   -  person jahroy    schedule 11.07.2012


Ответы (3)


Потому что, если их можно изменить, это может вызвать много проблем, учтите это:

public void count()
{
    int x;

    new Thread(new Runnable()
    {
        public void run()
        {
            while(x < 100)
            {
                x++;
                try
                {
                    Thread.sleep(1000);
                }catch(Exception e){}
            }
        }
     }).start();

     // do some more code...

     for(x = 0;x < 5;x++)
         for(int y = 0;y < 10;y++)
             System.out.println(myArrayElement[x][y]);
 }

Это грубый пример, но вы можете увидеть, где может возникнуть множество необъяснимых ошибок. Вот почему переменные должны быть окончательными. Вот простое решение проблемы, описанной выше:

public void count()
{
    int x;

    final int w = x;

    new Thread(new Runnable()
    {
        public void run()
        {
            int z = w;

            while(z < 100)
            {
                z++;
                try
                {
                    Thread.sleep(1000);
                }catch(Exception e){}
            }
        }
     }).start();

     // do some more code...

     for(x = 0;x < 5;x++)
         for(int y = 0;y < 10;y++)
             System.out.println(myArrayElement[x][y]);
 } 

Если вы хотите более полное объяснение, это вроде как синхронизировано. Java хочет, чтобы вы не ссылались на один объект из нескольких потоков. Немного о синхронизации:

Надеюсь, это помогло!

person John    schedule 11.07.2012
comment
Это не вызовет особых проблем — многие языки имеют полные замыкания :-) Тот факт, что Runnable (и, предположительно, потоки) находится в этой теме, ортогонален возможности изменения локальных переменных из анонимных внутренних типов. Даже если речь идет о чем-то, не имеющем отношения к многопоточности, это будут те же правила языка. Кроме того, та же проблема с потоками может возникнуть из-за переменных-членов. - person ; 11.07.2012
comment
В вашем Вот простое решение проблемы, описанной выше: разве w++ не выдаст вам ошибку компиляции?! Вы не можете переназначить w после его инициализации. - person mskw; 14.07.2014
comment
@mskw Я не могу поверить, что до сих пор никто ничего не сказал, спасибо, что нашел это. - person John; 14.07.2014

Потому что так говорит спецификация языка. По словам Гая Стила, причина этого выбор заключается в том, что программисты ожидают, что объявление int x = 0 в методе приведет к выделению памяти в стеке, но если вы можете вернуть new myRun() из метода (или иначе позволить myRun сохраняться после возврата функции) и вы можете изменить его впоследствии, тогда Вместо этого x должно быть выделено в куче, чтобы иметь семантику, которую вы ожидаете.

Они могли бы сделать это, и на самом деле другие языки сделали это именно так. Но вместо этого разработчики Java решили потребовать, чтобы вы помечали x как final, чтобы не требовать от реализации выделения в куче того, что выглядит как хранилище, выделенное в стеке.

(Я должен отметить: это не относится к Runnable. Это относится к любому анонимному внутреннему классу.)

person jacobm    schedule 11.07.2012

Большая «проблема» с многопоточностью, а также вся причина ее использования, заключается в том, что несколько вещей происходят одновременно. Внезапно значение любой переменной, к которой обращается ваш поток и которая не является локальной для потока, может измениться в любой момент. Таким образом, вы можете подумать, что просто печатаете числа 1-10 с помощью этого кода:

int x = 0;  //supposing that this was allowed to be non-final...
   private class myRun implements Runnable{

    @Override
    public void run() {
        for (int i=0; i<10; i++ ) {
            System.Out.Println( x++ );
        }
    }
}

Но на самом деле, если другой код в этом классе изменит значение x, вы можете в конечном итоге напечатать 230498 - 230508. Значение x может измениться в середине вашего цикла. Если вы не можете полагаться на то, что x имеет определенное значение или сохраняет значение, которое вы ему ранее присвоили, становится бесполезным использовать его в вашем коде. Зачем вам использовать переменную, если ее содержимое может измениться в мгновение ока?

Вместо того, чтобы просто запретить вам использовать его вообще, Java требует, чтобы вы сделали его final. Вы можете просто «пообещать» никогда не изменять значение x из другого потока, но тогда почему бы не сделать его final в первую очередь и позволить компилятору помочь вам? Конечно, вы можете получить доступ только к начальному значению, присвоенному x, но просто иметь возможность доступа к начальному значению переменной лучше, чем вообще не использовать ее, что фактически отрезало бы возможность потока использовать данные из остальных. вашего класса.

person Gordon Gustafson    schedule 11.07.2012