1. Introduction
1.绪论
Before we introduced thread-safety, and how it can be achieved.
之前我们介绍了线程安全,以及如何实现。
In this article, we’ll take a look at local variables and why they are thread-safe.
在这篇文章中,我们将看看局部变量以及为什么它们是线程安全的。
2. Stack Memory and Threads
2.堆栈内存和线程
Let’s start with a quick recap of the JVM memory model.
让我们先快速回顾一下JVM的内存模型。
Most importantly, the JVM splits up its available memory into stack and heap memory. Firstly, it stores all objects on the heap. Secondly, it stores local primitives and local object references on the stack.
最重要的是,JVM将其可用内存分成堆和堆内存。首先,它将所有对象存储在堆中。其次,它将本地基元和本地对象引用存储在堆栈中。
In addition, it’s important to realize that every thread, including the main thread, has its own private stack. Therefore, other threads do not share our local variables, which is what makes them thread-safe.
此外,重要的是要认识到,每个线程,包括主线程,都有自己的私有堆栈。因此,其他线程不会共享我们的局部变量,这就是线程安全的原因。
3. Example
3.例子
Let’s now continue with a small code example containing a local primitive and a (primitive) field:
现在让我们继续看一个包含本地基元和(基元)字段的小代码例子。
public class LocalVariables implements Runnable {
private int field;
public static void main(String... args) {
LocalVariables target = new LocalVariables();
new Thread(target).start();
new Thread(target).start();
}
@Override
public void run() {
field = new SecureRandom().nextInt();
int local = new SecureRandom().nextInt();
System.out.println(field + ":" + local);
}
}
On line five, we instantiate one copy of the LocalVariables class. On the next two lines, we start two threads. Both will execute the run method of the same instance.
在第五行,我们实例化了一个LocalVariables类的副本。在接下来的两行中,我们启动了两个线程。两者都将执行同一实例的run方法。
Inside the run method, we update the field field of the LocalVariables class. Secondly, we see an assignment to a local primitive. Finally, we print the two fields to the console.
在run方法里面,我们更新了LocalVariables类的field字段。其次,我们看到对一个本地基元的赋值。最后,我们将这两个字段打印到控制台。
Let’s take a look at the memory location of all the fields.
让我们看一下所有字段的内存位置。
First, the field is a field of class LocalVariables. Therefore, it lives on the heap. Secondly, the local variable number is a primitive. Consequently, it’s located on the stack.
首先,field是LocalVariables类的一个字段。因此,它存在于堆中。其次,局部变量number是一个基元。因此,它位于堆栈上。
The println statement is where things could go wrong when running the two threads.
println语句是运行两个线程时可能出错的地方。
First, the field field has a high probability of causing trouble since both the reference and the object live on the heap and are shared between our threads. The primitive local will be okay since the value lives on the stack. Consequently, the JVM does not share local between threads.
首先,field字段很有可能造成麻烦,因为引用和对象都生活在堆上,并且在我们的线程之间共享。原始的local将是好的,因为值住在栈上。因此,JVM不会在线程之间共享local。
So when executing we could, for example, have the following output:
因此,当执行时,我们可以,例如,有以下输出。
821695124:1189444795
821695124:47842893
In this case, we can see that we indeed had a collision between the two threads. We affirm this since it’s highly unlikely that both threads generated the same random integer.
在这种情况下,我们可以看到,我们确实在两个线程之间发生了碰撞。我们肯定了这一点,因为两个线程产生相同的随机整数的可能性非常小。
4. Local Variables Inside Lambdas
4.兰姆达内的局部变量
Lambdas (and anonymous inner classes) can be declared inside a method and can access the method’s local variables. However, without any additional guards, this could lead to a lot of trouble.
Lambdas(以及匿名内部类)可以在方法内部声明,并且可以访问该方法的局部变量。然而,如果没有任何额外的防护措施,这可能会导致很多麻烦。
Before JDK 8, there was an explicit rule that anonymous inner classes could only access final local variables. JDK 8 introduced the new concept of effectively final, and the rules have been made less strict. We’ve compared final and effectively final before and we’ve also discussed more on effectively final when using lambdas.
在 JDK 8 之前,有一条明确的规则:匿名的内部类只能访问final局部变量。JDK 8 引入了 effective final 的新概念,规则变得不那么严格了。我们之前已经对final和effectively final进行了比较,我们也对使用lambdas时的effectively final进行了更多讨论。
The consequence of this rule is that fields that are accessed inside lambdas have to be final or effectively final (they are thus not changed) which makes them thread-safe because of immutability.
这条规则的后果是,在lambdas内部访问的字段必须是final或有效final(因此它们不会被改变),这使得它们成为线程安全的,因为不可变。
We can see this behavior in practice in the following example:
我们可以在下面的例子中看到这种行为的实践。
public static void main(String... args) {
String text = "";
// text = "675";
new Thread(() -> System.out.println(text))
.start();
}
In this case, uncommenting the code on line 3 will cause a compilation error. Because then, the local variable text is no longer effectively final.
在这种情况下,取消对第3行代码的注释将导致编译错误。因为此时,局部变量text不再是有效的最终变量。
5. Conclusion
5.总结
In this article, we looked at the thread-safety of local variables and saw that this is a consequence of the JVM memory model. We also looked into the usage of local variables in combination with lambdas. The JVM guards their thread-safety by demanding immutability.
在这篇文章中,我们研究了局部变量的线程安全问题,发现这是JVM内存模型的一个结果。我们还研究了局部变量与lambdas的结合使用。JVM通过要求不可变性来保护其线程安全。
As always, the full source code of the article is available over on GitHub.
一如既往,该文章的完整源代码可在GitHub上获得。