局部变量的java线程安全

fquxozlt  于 2021-06-29  发布在  Java
关注(0)|答案(4)|浏览(374)

局部变量总是线程安全的。但是请记住,局部变量所指向的对象可能不是这样。如果对象是在方法中示例化的,并且从不转义,那么就不会有问题。
我对java中的多线程还不熟悉,我不明白“对象转义方法”是什么意思。有人能给我举一个这个语句的代码例子吗?在这个例子中,对象通过转义方法变成了非线程安全的。另外,请解释为什么它成为非线程安全的
这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的?
注:这听起来像是一个简单的问题,你们许多人。但对我来说,我读了多篇文章来查看一个代码示例,但找不到它。

izj3ouym

izj3ouym1#

如果你一点一点地看,应该不难理解。
如果程序中的两个或多个线程访问相同的数据(也称为“共享数据”),那么您需要确保代码以“线程安全”的方式访问数据。
java编程语言与c++等其他语言不同,它使得线程无法共享局部变量。唯一可以共享的变量是 static 变量和共享对象的成员变量(又称“字段”)。
在函数调用中将对象引用作为参数传递本身并不允许对象引用“转义”到另一个线程。但它可以允许。这完全取决于被调用函数对您传递的对象做了什么。如果在多线程程序中编写调用函数的代码,那么您有责任知道函数将如何处理您提供给它的对象(例如,函数是否将与另一个线程共享对象)
对象共享的某些方式:
将对对象的引用存储到 static 线程共享的变量,
对象是 Runnable 你给一个新的 Thread ,或者它是 Runnable 对象的成员变量,
对象是一个 Runnable 或者 Callable 提交到线程池,或者是提交到线程池的对象的成员变量的引用,
对象是以前共享的某个其他对象的成员变量的引用,包括,尤其是,
将对象添加到共享的容器(例如 List 或者 Set 或者 Map ,
你把物体放进 Queue 特别是为在线程之间共享对象而设置的。

hjzp0vay

hjzp0vay2#

这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的?
对。
嗯,它可能会变成非线程安全的:这取决于其他方法对它做了什么。
非线程安全的示例如下:

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

这仍然是线程安全的(如果使用局部变量作为参数调用),因为 param 以线程限制的方式使用(当前执行线程之外的任何东西都看不到它的值)。
您甚至可以在方法中对其进行变异:

void stillSafe2(List<String> param) {
  param.add("Hello!");
}

尽管发生了变异,但这仍然是线程安全的,因为仍然只有当前线程才能看到值。

sauutmhj

sauutmhj3#

java是一种基于引用的语言。那只是指针的另一个词。
当你写:

String foo = "hello";

这只是语法:

String foo; // [1]
foo = "hello"; // [2]

第1行声明了一个名为“foo”的变量(指针)。第2行做了两件事:它创建了一个全新的对象。 foo 然后更新以引用它。就像 "hello" 是宝藏 foo 是一张藏宝图。 foo = "Hello" 创造新的宝藏,把它埋在沙子里,然后在你的纸上画一张宝藏的Map;你贴上标签的那张纸 foo . "“foo”不是宝藏,说这个字符串是“foo”字符串是不正确的。不是-只是宝藏,宝藏没有名字。不可能有Map指向它(这意味着垃圾收集器最终会将它挖出来并将其清除),可能有上千个Map指向它,以及介于两者之间的任何东西。在上面的代码片段中,有一个Map指向它。你的Map。你打电话的那个 foo .
但你可以和其他人分享这张Map。然后他们也能找到你的宝藏。
“局部变量是不可变的”。对。他们是。不过,你当地人是这样认为的 foo 变量,以及 foo 不是宝藏。是Map。这是你的Map。没人能搞乱它。唯一能让Map看起来与众不同的方法就是你写下 foo = 在你自己的代码里。没有多少过关 foo 使用其他方法会改变Map。
你读到的文字所指的是,唯一不变的东西就是你的Map。Map指向的宝藏?谁知道呢。如果你把Map交给其他人,他们不能改变你的Map,但他们可以复制它。他们可以跟着。他们可以挖到宝藏,然后把它砸碎。它们不会影响你的Map,但是如果你按照你的Map走,你会发现什么?如果你和其他人分享宝藏的位置,现在可能完全不同了。
现在,对于琴弦来说,这是一个没有实际意义的观点:琴弦的宝藏是不可战胜的。它们不能以任何方式被粉碎或修改。它们是不可变的对象,没有改变它的方法,也没有公共(非final)字段。但并非所有物体都是这样。有些宝藏可以换,也可以打碎。例如,一个简单的列表:

List<String> x = new ArrayList<String>();
x.add("Hello");
foo(x);
System.out.println(x);

在上面的代码中,您不知道它打印什么,因为您不知道foo做什么。你知道你的 x 无法打印 null . 空是指你有一张空白的藏宝图,没有人能弄乱你的藏宝图。但是 foo 可以捣乱宝藏:

public void foo(List<String> x) {
    x = List.of();
}

这没什么用。这个方法可以得到一份藏宝图。然后它创造新的宝藏,把它埋在沙子里,把一块橡皮擦放到它的口袋里 x 藏宝图(以前是你的一个副本),然后在上面画一个全新的藏宝图,其中x表示新创建的宝藏的位置。这对你的Map,或者你的Map所指向的宝藏,绝对没有任何影响。您的代码将继续打印 [Hello] .
然而:

public void foo(List<String> x) {
    x.add("World");
}

这是完全不同的。这是你的藏宝图,你给它的副本,跟随它并向下挖掘( . javaese的意思是:跟着Map挖)。然后它打开箱子,把“世界”放在里面(从技术上讲,它把“世界”宝藏的Map放在里面,字符串也是对象,因此是基于参考的)。
如果你以后跟着Map挖,你会看到的。你的代码会打印出来 [Hello, World] .
这就是课文所说的。通过复制,或者使用不可变的对象,或者意识到如果您共享了Map,Map所指向的任何宝藏都可能已被其他代码更改,来避免这种情况。

v1uwarro

v1uwarro4#

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

相关问题