为什么局部变量在Java中是线程安全的


90

我在用Java阅读多线程,碰到了这个问题

局部变量在Java中是线程安全的。

从那时起,我一直在思考局部变量如何/为什么是线程安全的。

有人可以让我知道吗。


27
因为它们是在堆栈中分配的。和线程不共享堆栈..每个线程都唯一。
Rohit Jain 2012年

Answers:


103

创建线程时,它将创建自己的堆栈。两个线程将具有两个堆栈,并且一个线程永远不会与其他线程共享其堆栈。

程序中定义的所有局部变量都将在堆栈中分配内存(正如Jatin所说,内存是指对象的引用值和原始类型的值)(线程的每个方法调用都会在其自己的堆栈上创建堆栈框架)。该线程执行完方法后,将立即删除堆栈框架。

YouTube上斯坦福大学教授的精彩演讲,这可能有助于您理解这一概念。


13
抱歉,您错了,只有原始局部变量存储在堆栈中。其余所有变量都存储在堆中。Java 7引入了转义分析,对于某些变量,转义分析可能会将其分配到堆栈中
-Jatin

6
堆栈仅保存对堆上对象的引用。因为堆栈被清除,所以引用也被清除。因此可用于垃圾收集
Jatin

6
@Jatin:你是对的。当我指内存时,我指的是对象的引用值和基元的值(我认为新手开发人员也知道对象在堆上)。
kosa 2013年

2
@Nambari,但如果参考值指向共享变量。那我们怎么可以说它是线程安全的呢?
H.Rabiee 2014年

3
@hajder:是什么使变量成为共享变量?从那里开始。实例变量或类变量对吗?不是局部变量,而是在该线程中阅读了Marko Toplink的答案,我认为这是您感到困惑的地方。
科萨

19

局部变量存储在每个线程自己的堆栈中。这意味着局部变量永远不会在线程之间共享。这也意味着所有本地原始变量都是线程安全的。

public void someMethod(){

   long threadSafeInt = 0;

   threadSafeInt++;
}

对对象的本地引用有些不同。引用本身未共享。但是,引用的对象没有存储在每个线程的本地堆栈中。所有对象都存储在共享堆中。如果本地创建的对象从不逃脱其创建方法,则该线程是安全的。实际上,您也可以将其传递给其他方法和对象,只要这些方法或对象都不使传递的对象可用于其他线程


有错误的看法,请看@Nambari回应的评论
Jatin

如果您要指出的是localSafeInt始终为0,那么无论如何先删除1再删除就可以了。因此,它表明此变量不会在线程之间共享,因此不会受到多线程的影响。.我想您可以指出一点,线程安全始终只是0或1
tObi

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
您说“在它所引用的对象上调用方法本质上不是线程安全的”。但是,由方法本地引用引用的对象(在此方法范围内实例化)如何由两个线程共享?您能举例说明吗?
Akshay Lokur,2014年

1
局部变量可以保存也可以不保存方法范围内实例化的对象,这不是问题的一部分。即使是,该方法也可以访问共享状态。
Marko Topolnik,2014年

6

除了诸如Nambari的其他答案。

我想指出的是,您可以在匿名类型方法中使用局部变量:

可以在可能损害线程安全性的其他线程中调用此方法,因此java强制将用于匿名类型的所有局部变量声明为final。

考虑以下非法代码:

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#通过“ closures”所做的那样),则局部变量将不再在所有情况下都是线程安全的。在这种情况下,i不能保证所有线程末尾的值是100


韦斯顿,您好,从上面的讨论和下面的答案中,我了解到Java确保了所有局部变量的线程安全。请问然后,synchronized关键字的实际用途是什么?你能用这样的例子解释一下吗?
Prabhu

5

线程将拥有自己的堆栈。两个线程将具有两个堆栈,并且一个线程永远不会与其他线程共享其堆栈。局部变量存储在每个线程自己的堆栈中。这意味着局部变量永远不会在线程之间共享。


3

在Java中,基本上有四种存储类型可以存储类信息和数据:

方法区域,堆,JAVA堆栈,PC

因此,方法区域和堆是由所有线程共享的,但是每个线程都有自己的JAVA堆栈和PC,而其他任何线程都不共享。

Java中的每个方法都是作为Stack frame。因此,当线程调用一种方法时,会将堆栈框架加载到其JAVA堆栈上。PC将在方法的字节码中包含要执行的下一条指令的信息。所以所有的局部变量都是THREAD SAFE。

@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;
}

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.