Почему локальные переменные в Java являются потокобезопасными

91

Я читал многопоточность на Java и наткнулся на это

Локальные переменные в Java являются потокобезопасными.

С тех пор я думал, как и почему локальные переменные являются потокобезопасными.

Может кто-нибудь дайте мне знать.

Ананд
источник
27
Потому что они размещены в стеке. И потоки не имеют общего стека .. он уникален для каждого ..
Рохит Джайн

Ответы:

103

Когда вы создаете поток, он будет иметь свой собственный стек. Два потока будут иметь два стека, и один поток никогда не делит свой стек с другим потоком.

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

На YouTube есть отличная лекция профессора Стэнфорда, которая может помочь вам понять эту концепцию.

коса
источник
13
Извините, вы ошибаетесь, в стеке хранятся только примитивные локальные переменные. Остальные все переменные хранятся в куче. В Java 7 появился escape-анализ, который для некоторых переменных может размещать его в стеке
Jatin
6
Стек содержит только ссылку на объект в куче. Поскольку стек очищается, ссылка также очищается. следовательно, он доступен для сборки мусора
Jatin
6
@Jatin: Вы правы. Когда я имел в виду память, я имел в виду ссылочное значение для объектов и значения для примитивов (я думаю, что начинающие разработчики также знают, что объекты находятся в куче).
kosa
2
@Nambari, но если ссылочное значение указывает на общую переменную. Тогда как мы можем сказать, что это потокобезопасный?
H.Rabiee
3
@hajder: Что делает переменную разделяемой? начать оттуда. Переменные экземпляра или класса, верно? не локальные переменные И прочитайте ответ Марко Топлинка в этой теме, я думаю, что это то, что вас смущает.
kosa
19

Локальные переменные хранятся в собственном стеке каждого потока. Это означает, что локальные переменные никогда не используются совместно между потоками. Это также означает, что все локальные примитивные переменные являются потокобезопасными.

public void someMethod(){

   long threadSafeInt = 0;

   threadSafeInt++;
}

Локальные ссылки на объекты немного отличаются. Сама ссылка не передается. Однако указанный объект не сохраняется в локальном стеке каждого потока. Все объекты хранятся в общей куче. Если объект, созданный локально, никогда не ускользает от метода, в котором он был создан, он является потокобезопасным. Фактически, вы также можете передать его другим методам и объектам, если ни один из этих методов или объектов не сделает переданный объект доступным для других потоков.

Ренджит
источник
Существует ошибка в agrument, пл посмотреть на комментарии ответа @Nambari
Jatin
Если вы указываете на тот факт, что localSafeInt всегда будет просто 0, затем 1, а затем все равно будет удален, это хорошо. Таким образом, это показывает, что эта переменная не используется совместно между потоками и, следовательно, на нее не влияет многопоточность .. Я думаю, вы могли бы указать на это немного больше, что потокобезопасность всегда равна 0 или 1
tObi 04
14

Подумайте о методах как об определениях функциональности. Когда два потока выполняют один и тот же метод, они никоим образом не связаны. Каждый из них создаст свою собственную версию каждой локальной переменной и не сможет каким-либо образом взаимодействовать друг с другом.

Если переменные не являются локальными (например, переменные экземпляра, определенные вне метода на уровне класса), они прикрепляются к экземпляру (а не к отдельному запуску метода). В этом случае два потока, выполняющие один и тот же метод, видят одну переменную, и это не является потокобезопасным.

Рассмотрим эти два случая:

public class NotThreadsafe {
    int x = 0;
    public int incrementX() {
        x++;
        return x;
    }
}

public class Threadsafe {
    public int getTwoTimesTwo() {
        int x = 1;
        x++;
        return x*x;
    }
}

В первом случае два потока, запущенные в одном экземпляре, NotThreadsafeбудут видеть один и тот же x. Это может быть опасно, потому что потоки пытаются изменить x! Во втором случае два потока, запущенные в одном экземпляре, Threadsafeбудут видеть совершенно разные переменные и не могут влиять друг на друга.

Кори Кендалл
источник
6

У каждого вызова метода есть свои собственные локальные переменные, и, очевидно, вызов метода происходит в одном потоке. Переменная, которая обновляется только одним потоком, по своей сути является потокобезопасной.

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

Марко Топольник
источник
1
Вы говорите, что «вызов методов объекта, на который он ссылается, по своей сути не является потокобезопасным». Но как объект, на который ссылается локальная ссылка метода, созданный в этой области метода, может совместно использоваться двумя потоками? Не могли бы вы указать своим примером?
Акшай Локур
1
Локальная переменная может содержать или не содержать объект, созданный в пределах области действия метода, что не было частью вопроса. Даже если это так, метод может получить доступ к общему состоянию.
Марко Топольник
6

В дополнение к другим ответам, таким как Nambari's.

Хочу отметить, что вы можете использовать локальную переменную в методе анонимного типа:

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

Рассмотрим этот незаконный код:

public void nonCompilableMethod() {
    int i=0;
    for(int t=0; t<100; t++)
    {
      new Thread(new Runnable() {
                    public void run() {
                      i++; //compile error, i must be final:
                      //Cannot refer to a non-final variable i inside an
                      //inner class defined in a different method
                    }
       }).start();
     }
  }

Если бы Java действительно позволяла это (как это делает C # через «замыкания»), локальная переменная больше не будет потокобезопасной при любых обстоятельствах. В этом случае значение iв конце всех потоков не гарантируется 100.

Вестон
источник
Привет Вестон! Из приведенного выше обсуждения и ответов ниже я понял, что Java обеспечивает безопасность потоков для всех локальных переменных. Могу ли я тогда узнать, как на самом деле используется ключевое слово synchronized? не могли бы вы объяснить на примере, подобном этому.
Прабху
5

У потока будет свой стек. Два потока будут иметь два стека, и один поток никогда не делит свой стек с другим потоком. Локальные переменные хранятся в собственном стеке каждого потока. Это означает, что локальные переменные никогда не используются совместно между потоками.

Судхиркумар Муркуте
источник
3

В основном в java есть четыре типа хранилища для хранения информации о классах и данных:

Область метода, куча, стек JAVA, ПК

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

Каждый метод в java представляет собой фрейм стека. поэтому, когда один метод вызывается потоком, этот кадр стека загружается в его стек JAVA. Все локальные переменные, которые находятся в этом кадре стека и связанный стек операндов, не используются другими. ПК будет иметь информацию о следующей инструкции для выполнения в байтовом коде метода. поэтому все локальные переменные БЕЗОПАСНЫ ДЛЯ ПОТОКОВ.

@Weston также дал хороший ответ.

Прашант
источник
1

В стеке потока хранятся только локальные переменные.

Локальная переменная , которая является primitive type(например , INT, долго ...) хранится на thread stackи в результате - другой поток не имеет к нему доступ.

Локальная переменная , которая является reference type(правопреемником Object) содержит от 2 -х частей - адрес (который хранится на thread stack) и объект (который хранится на heap)


class MyRunnable implements Runnable() {
    public void run() {
        method1();
    }

    void method1() {
        int intPrimitive = 1;

        method2();
    }

    void method2() {
        MyObject1 myObject1 = new MyObject1();
    }
}

class MyObject1 {
    MyObject2 myObject2 = new MyObject2();
}

class MyObject2 {
    MyObject3 myObject3 = MyObject3.shared;
}

class MyObject3 {
    static MyObject3 shared = new MyObject3();

    boolean b = false;
}

введите описание изображения здесь

yoAlex5
источник