有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

局部变量的java线程安全

Local variables are always thread safe. Keep in mind though, that the object a local variable points to, may not be so. If the object was instantiated inside the method, and never escapes, there will be no problem.

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

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

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


共 (4) 个答案

  1. # 1 楼答案

    Does it mean that if we simply pass this object to another method it will become non thread safe?

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

    非线程安全的示例如下:

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

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

  2. # 2 楼答案

    如果你一件一件地看,应该不难理解

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

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

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


    对象共享的一些方式:

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

    • 该对象是Runnable对象,您将其赋予一个新的Thread,或者它是Runnable对象的一个成员变量的引用对象

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

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

      • 将对象添加到共享的容器中(例如,ListSetMap
      • 您将对象放入一个Queue中,该设置专门用于在线程之间共享对象
  3. # 3 楼答案

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

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

  4. # 4 楼答案

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

    当你写作时:

    String foo = "hello";
    

    这只是语法:

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

    第1行声明了一个名为“foo”的变量(指针)。 第2行做了两件事:它创建了一个全新的对象foo然后更新以引用它。就像"hello"是宝藏,而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);
    

    在上面的代码中,你不知道它打印什么,因为你不知道foo做什么。你知道你的x不可能打印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]

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