静态方法局部变量和线程安全性

10

在普通实例方法中,局部变量是线程安全的。

如果我在静态方法中有以下内容:

int i = 0;
i += 3;

这段代码是线程安全的吗?有什么需要注意的吗?

另外,当每个变量都有自己的堆栈时,这究竟意味着什么?这是否意味着它拥有自己的堆栈跟踪信息?

谢谢


1
参见:https://dev59.com/nnRC5IYBdhLWcg3wD83r 唯一的问题是如果 i 不是一个局部变量。 - Seth
或者如果他在其中放了另一个对象,该对象执行线程不安全的操作,例如在没有锁定的情况下设置静态字段之类的操作。按照逻辑,他的方法也不会是线程安全的。但这更像是一个棘手的例外情况 :) - Skurmedel
好的评论skurmedal。感谢链接Seth,不幸的是有时我提交帖子后需要一段时间才能出现相关链接。 - GurdeepS
4个回答

16
如果您发布的代码行位于方法内部,则没有理由它不是线程安全的。各个线程没有以任何方式进行交互-每个线程都看到一个不同的 i 。
如果尝试在线程之间共享 i 的值,例如通过将 i 设置为静态字段,那么可能会发生竞态条件,根据时间不同,可以获得不同的结果。
关于您的第二个问题,每个线程都有自己的堆栈,而不是每个变量。

7
首先,我假设“线程安全”指的是“在多个线程发生变化时,行为像某些操作是原子性的”。如果这不是你所说的“线程安全”,那么在询问与其相关的问题之前,请仔细定义“线程安全”。似乎几乎每个在Stack Overflow上询问线程安全的人都对其含义有不同的个人定义。
其次,局部变量是不安全的。特别是,作为lambda或匿名方法的封闭外部变量的局部变量,或者位于迭代器块内部的局部变量,在多个线程上被修改时无法保证线程安全。
不作为匿名函数的封闭外部变量,也不在迭代器块中的局部变量只能由当前线程修改,因此不会被多个线程同时修改。
另外,“每个变量拥有自己的堆栈”是什么意思?
我不知道这是什么意思;变量没有堆栈。线程有堆栈。

6

简短的回答是,下面的方法是线程安全的:

public static int CalcuateSomething( int number ) {
  int i = number * 10;
  return i;
}

只要本地变量不指向堆上的共享对象,它们就是安全的。

正如在单线程应用程序中一样,该方法的以下调用将返回不同的值。

CalculateSomething( 10 ) => 100
CalculateSomething( 20 ) => 200

为什么会这样呢?因为每次调用number方法时,都会取得不同的值,因此i的值也会随之改变。由于i是在堆栈上分配的,所以当函数结束时不会记住i的值。几乎所有语言的函数都是基于堆栈模型的。每次调用不同的方法时,当前方法会暂停,新方法被推入调用堆栈并被调用。当该方法完成时,程序会取消暂停调用方法并从返回地址继续执行(即在returnAddress处)。任何在方法内定义的本地变量都是该方法堆栈帧的一部分。我们上面的方法的堆栈帧可以像这样考虑:

public Class StackFrameForCalculateSomething implements StackFrame {
  public int ReturnAddress;
  public int i = 0;
  public int number;
}

栈可以被看作是一组StackFrame对象。

堆栈调用栈

每次调用新方法时,程序可能会执行以下操作:

StackFrameForCalculcateSomething s = new StackFrameForCalculateSomething();
s.returnAddress = instructionPointer;
s.number = 10;
callStack.push( s );
s.invoke();

StackFrameForCalculcateSomething s2 = new StackFrameForCalculateSomething();
s2.returnAddress = instructionPointer;
s2.number = 20;
callStack.push( s2 );
s2.invoke();

这对于线程意味着什么呢?在使用线程的情况下,你会有多个独立的调用堆栈(callStacks)和它们各自的集合。访问本地变量是安全的原因在于Thread 1的调用堆栈无法访问Thread 2的调用堆栈,因为它们是单独的对象。就像单线程情况下,s和s2是具有不同数值的不同对象一样,它们是相互独立的。假设s在Thread 1中,s2在Thread 2中,Thread 1和Thread 2之间没有共享任何内存,所以是线程安全的。
线程不共享它们的堆栈帧(stack frames),但它们共享堆(heap)。

0

这里有一个(简单的)示例演示,以帮助扩展Mark所说的内容:

void Main()
{   
    for (int x = 0; x < 10; x++)
    {
        ThreadPool.QueueUserWorkItem(z=> myClass.DoSomething());
    }

}
public class myClass
{
    public static void DoSomething()
    {
        int i = 0;
        Console.WriteLine (i += 3);
    }
}

输出结果: 3 3 3 3 3 3 3 3 3 3 3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接