【问题标题】:Thread safety of local variables局部变量的线程安全
【发布时间】:2020-12-27 16:35:55
【问题描述】:

局部变量总是线程安全的。但请记住, 局部变量指向的对象,可能不是这样。如果对象是 在方法内部实例化,从不逃逸,不会有 问题。

我是 Java 多线程的新手,我不明白“对象转义方法”是什么意思。谁能告诉我这个语句的代码示例,其中对象通过转义方法变得非线程安全。另外请解释为什么它变得非线程安全

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

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

【问题讨论】:

  • "这是否意味着如果我们简单地将这个对象传递给另一个方法,它将变得非线程安全?"是的。好吧,它可能变成非线程安全的:这取决于其他方法如何处理它。
  • @AndyTurner 你能提供一个代码示例吗?并解释为什么它变得线程安全
  • 至少就一般推理而言,可能仍然是被调用的方法没有对它做不安全的事情,但你不能确定。

标签: java multithreading thread-safety local-variables


【解决方案1】:

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

是的。

嗯,它可能变成非线程安全的:这取决于其他方法如何处理它。

一个非线程安全的例子是这样的:

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

尽管发生了变化,但这仍然是线程安全的,因为仍然只有当前线程可以看到该值。

【讨论】:

  • 谢谢.. 你能否解释一下局部变量是如何线程安全的。因为本地创建的对象驻留在堆中,而堆在不同线程之间共享。那么它将如何成为线程安全的@Andy Turner
  • @AlexBennet 局部变量不能被其他线程访问,因为它们是当前线程的本地变量。如果它们引用了由当前线程创建的值,并且您不让其他线程有任何访问该值的方法,那么其他线程就看不到它。这就像拥有一部只有您知道号码的电话。
  • 这是一个傻瓜,但non-thread safenon thread-safe 之间存在差异我相信讨论是关于局部变量是thread-safenon thread-safe
  • @WJS 我怀疑有什么混淆,因为除了“非线程安全”之外,我想不出“非线程安全”的其他含义。
  • 正是我的观点。我认为这意味着变量仅在不在线程中使用时才是安全的。但这就是您在上面第一句话中使用的内容(这就是我评论的原因)。
【解决方案2】:

Java 是一种基于references 的语言。这只是 pointers 的另一种说法。

当你写作时:

String foo = "hello";

这只是语法糖:

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

第 1 行声明了一个名为“foo”的变量(一个指针)。 第 2 行做了两件事:它创建了一个全新的对象。 foo 然后更新以引用它。就像"hello" 是宝藏,foo 是藏宝图。 foo = "Hello"创造新的宝藏,把它埋在沙子里,然后在你的纸上画出宝藏的地图;您标记为foo 的那张纸。 “foo”不是宝,说这个字符串是“foo”字符串是不正确的。不是——它只是宝物,而宝物是没有名字的。不可能有通向它的地图(这意味着垃圾收集器最终会挖掘它并摆脱它),可能有一千个通向它的地图,以及介于两者之间的任何东西。在上面的 sn-p 中,有一张地图通向它。你的地图。你打电话给foo的那个。

但您可以与他人共享该地图。然后他们也能找到你的宝藏。

'locals 是不可变的'。是的。他们是。但是,您的本地是foo 变量,而foo不是宝藏。是地图。这是你的地图。没有人能惹它。 map 看起来会有所不同的唯一方法是,如果您在自己的代码中的某个地方编写 foo = 。将foo 传递给其他方法的次数永远不会改变该映射。

您阅读的文字所指的是,唯一不可变的是您的地图。地图所指的宝藏?谁知道。如果您将地图交给其他人,他们无法更改您的地图,但他们可以复制它。他们可以跟随它。他们可以挖掘那个宝藏,并将其粉碎成碎片。它们不会影响你的地图,但如果你跟着地图走,你会在那里找到什么?如果您将宝藏的位置与其他任何人分享,现在可能会完全不同。

现在,对于弦乐来说,这是一个有争议的问题:弦乐宝藏是无敌的。它们不能以任何方式被粉碎或修改。它们是不可变的对象,没有改变它的方法,也没有公共(非最终)字段。但并不是所有的物体都是这样的。一些宝物可以改变或粉碎。比如一个简单的列表:

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

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

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

这不会做任何事情。此方法获取您的藏宝图的副本。然后它会创造新的宝藏,把它埋在沙子里,用橡皮擦到它的x 藏宝图(曾经保存你的副本),然后在上面绘制一个全新的藏宝图,X 标记了它的位置新创造的宝藏。这对您的地图或地图所指向的宝藏绝对没有任何作用。您的代码将继续打印[Hello]

但是:

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

这是完全不同的。这将获取您提供的藏宝图副本,跟随它并挖掘(. 是 java-ese 为:跟随地图并挖掘)。然后它打开箱子,把“世界”放在那里(从技术上讲,它把“世界”宝藏的地图放在那里,字符串也是对象,因此是基于引用的)。

如果您稍后按照地图进行挖掘,您就会看到这一点。您的代码将打印[Hello, World]

这就是文字所要表达的。通过制作副本、使用不可变对象或意识到如果您已共享地图,您的地图指向的任何宝藏可能已被其他代码更改的概念来避免这种情况。

【讨论】:

    【解决方案3】:

    一块一块看应该不难理解。

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

    与 C++ 等其他一些语言不同,Java 编程语言使得线程不可能共享局部变量。唯一可以共享的变量是static 变量,以及共享对象的成员变量(也称为“字段”)。

    在函数调用中将对象引用作为参数传递本身并不允许对象引用“逃逸”到另一个线程。但它可以允许它。这完全取决于被调用函数对您传递的对象的作用。如果您在多线程程序中编写调用函数的代码,有责任了解该函数将如何处理您提供给它的对象(例如,该函数是否会与另一个线程与否。)


    对象可以共享的一些方法:

    • 您将对对象的引用存储到线程共享的static 变量中,

    • 该对象是您赋予新的ThreadRunnable 对象,或者它是Runnable 对象的成员变量之一的所指对象,

    • 对象是Runnable或您提交给thread poolCallable,或者它是您提交给线程池的对象的成员变量的引用,

    • 该对象是以前共享的某个其他对象的成员变量的引用,尤其是,

      • 您将对象添加到共享容器(例如,ListSetMap
      • 您将对象放入一个Queue 中,该Queue 是专门为在线程之间共享对象而设置的。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2017-03-07
        • 2015-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-24
        相关资源
        最近更新 更多