Java 局部变量的线程安全性

Java 局部变量的线程安全性,java,multithreading,thread-safety,local-variables,Java,Multithreading,Thread Safety,Local Variables,局部变量总是线程安全的。但要记住的是 局部变量指向的对象,可能不是这样。如果对象是 在方法内部实例化,并且永不转义,因此 问题 我不熟悉Java中的多线程,我不明白“对象转义方法”是什么意思。任何人都可以给我看一个代码示例,其中对象通过转义方法变得非线程安全。另外,请解释为什么它变得非线程安全 这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的 注:对你们中的许多人来说,这听起来可能是一个简单的问题。但对我来说,我读了多篇文章来查看代码示例,但都找不到 这是否意味着如果我

局部变量总是线程安全的。但要记住的是 局部变量指向的对象,可能不是这样。如果对象是 在方法内部实例化,并且永不转义,因此 问题

我不熟悉Java中的多线程,我不明白“对象转义方法”是什么意思。任何人都可以给我看一个代码示例,其中对象通过转义方法变得非线程安全。另外,请解释为什么它变得非线程安全


这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的

注:对你们中的许多人来说,这听起来可能是一个简单的问题。但对我来说,我读了多篇文章来查看代码示例,但都找不到


这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的

嗯,它可能会变成非线程安全的:这取决于其他方法对它做了什么

非线程安全的示例如下:

class YourClass {
  List<String> field;

  void foo() {
    List<String> local = new ArrayList<>(List.of("foo"));
    unsafe(local);        
  }

  void unsafe(List<String> param) {
    field = param;
  }
}
void stillSafe(List<String> param) {
  System.out.println(param);
}

尽管有变异,这仍然是线程安全的,因为仍然只有当前线程可以看到值。

转义方法意味着:它被使用或存储在方法之外的某个地方


示例:您有一个全局变量计数器,并且在本地方法中计算一些内容。现在,只要使用本地计数器,一切都很好,但只要设置globalcounter=localcounter,就不能假设globalcounter==localcounter,因为它总是可以被外部因素更改。

Java是一种基于引用的语言。这只是指针的另一个词

当你写作时:

String foo = "hello";
这只是语法:

String foo; // [1]
foo = "hello"; // [2]
第1行声明了一个名为“foo”的变量(指针)。 第2行做了两件事:它创建了一个全新的对象<然后更新代码>foo以引用它。就像
“你好”
是宝藏,
foo
是一张藏宝图
foo=“Hello”
创建新的宝藏,将其埋在沙子中,然后在纸上绘制宝藏地图;贴上标签的那张纸
foo
。“foo”不是宝藏,说这个字符串是“foo”字符串是不正确的。它不是——它只是宝藏,宝藏没有名字。不可能有指向它的地图(这意味着垃圾收集器最终会将它挖出来并将其清除),可能有一千个指向它的地图,以及介于两者之间的任何东西。在上面的代码片段中,有一个映射指向它。你的地图。您称之为
foo
的那个

但是你可以和其他人分享这张地图。然后他们也能找到你的宝藏

“本地人是不可变的”。对他们是。但是,您的本地变量是
foo
变量,
foo
而不是宝藏。这是地图。这是你的地图。没人能搞乱它。映射看起来会有任何不同的唯一方式是,如果您在自己的代码中的某个地方编写
foo=
。再多的传递
foo
到其他方法都不会改变这个映射

你读到的文本所指的是,唯一不变的东西是你的地图。地图指向的宝藏?谁知道呢。如果你把地图交给其他人,他们不能更改你的地图,但他们可以复制。他们可以跟随它。他们可以挖掘宝藏,然后把它砸碎。它们不会影响你的地图,但如果你按照你的地图走,你会发现什么?现在可能完全不同了,如果你和其他人分享宝藏的位置

现在,对于弦乐来说,这是一个没有实际意义的观点:弦乐宝藏是不可战胜的。它们不能以任何方式被粉碎或修改。它们是不可变的对象,没有改变它的方法,也没有公共(非最终)字段。但并非所有对象都是这样。有些宝藏可以改变或打碎。例如,一个简单的列表:

List<String> x = new ArrayList<String>();
x.add("Hello");
foo(x);
System.out.println(x);
这没有任何作用。此方法可获得您的藏宝图副本。然后它创造新的宝藏,把它埋在沙子里,用橡皮擦擦去它的
x
宝藏地图(用来保存你的一份),然后在上面画一张全新的宝藏地图,其中x标记了新创造的宝藏的位置。这绝对不会对你的地图或你的地图所指向的宝藏产生任何影响。您的代码将继续打印
[Hello]

然而:

public void foo(List<String> x) {
    x.add("World");
}
public void foo(列表x){
x、 添加(“世界”);
}
这是完全不同的。这将获取您给它的藏宝图副本,跟随它并向下挖掘(
是java ese for:Follow the map and dig)。然后它打开箱子,把“世界”放在里面(从技术上讲,它把“世界”宝藏的地图放在里面,字符串也是对象,因此是基于引用的)

如果你以后跟着地图挖,你会看到的。您的代码将打印
[你好,世界]


这就是课文所说的。通过复制、使用不可变对象或意识到地图所指向的任何宝藏可能已被其他代码更改(如果您共享了地图)来避免它。

如果您逐个查看地图,应该不难理解

如果程序中的两个或多个线程访问相同的数据(也称为“共享数据”),则需要确保代码以“线程安全”的方式访问数据

Java编程语言与其他一些语言(如C++)不同,使得java无法共享本地变量。唯一可以共享的变量是

静态
变量,以及共享对象的成员变量(也称为“字段”)

在函数调用中将对象引用作为参数传递本身并不允许对象引用“转义”到另一个线程。但它可以允许这样做。这完全取决于被调用函数对对象所做的操作
public void foo(List<String> x) {
    x = List.of();
}
public void foo(List<String> x) {
    x.add("World");
}