局部变量总是线程安全的。但请记住, 局部变量指向的对象,可能不是这样。如果对象是 在方法内部实例化,永不转义,不会有 问题。
我是 Java 多线程的新手,我不明白“对象转义方法”是什么意思。任何人都可以向我展示此语句的代码示例,其中对象通过转义方法变得非线程安全。另外请解释为什么它变得非线程安全
这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的?
注意:对你们中的许多人来说,这听起来可能是一个简单的问题。但对我来说,我阅读了多篇文章以查看代码示例,但找不到。
答案 0 :(得分:3)
这是否意味着如果我们简单地将这个对象传递给另一个方法,它将成为非线程安全的?
是的。
好吧,它可能变得非线程安全:这取决于其他方法如何处理它。
一个非线程安全的例子是这样的:
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!");
}
尽管发生了变化,但这仍然是线程安全的,因为仍然只有当前线程可以看到该值。
答案 1 :(得分:1)
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
。 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]
。
这就是正文所讨论的内容。通过制作副本或使用不可变对象来避免这种情况,或者意识到如果您共享了地图,则您的地图指向的任何宝藏可能已被其他代码更改的概念。
答案 2 :(得分:1)
逐条看应该不难理解。
如果程序中的两个或多个线程访问相同的数据(也称为“共享数据”),那么您需要确保您的代码以“线程安全”的方式访问数据。
与 C++ 等其他一些语言不同,Java 编程语言使得线程不可能共享局部变量。唯一可以共享的变量是 static
变量,以及共享对象的成员变量(也称为“字段”)。
在函数调用中将对象引用作为参数传递本身并不会允许对象引用“转义”到另一个线程。但它可以允许它。这一切都取决于被调用的函数对您传递的对象做了什么。如果您编写的代码在多线程程序中调用函数,则您有责任知道该函数将如何处理您提供给它的对象(例如,该函数是否将与另一个线程与否。)
对象可以共享的一些方式:
您将对该对象的引用存储到由线程共享的 static
变量中,
对象是您赋予新的 Runnable
的 Thread
对象,或者它是 Runnable
对象的成员变量之一的所指,
对象是您提交给 thread pool 的 Runnable
或 Callable
,或者它是您提交给一个对象的成员变量的指涉对象线程池,
该对象是先前共享的某个其他对象的成员变量的指代对象,尤其是
List
或 Set
或 Map
,Queue
中,该 showDialog
专为在线程之间共享对象而设置。答案 3 :(得分:0)
对方法进行转义意味着:它被使用或存储在方法之外的某个地方。
示例:您有一个全局变量 counter 并且您在本地方法中计算某些内容。现在只要你使用本地计数器一切都很好,但是一旦你设置了 globalcounter = localcounter 你就不能假设 globalcounter == localcounter,因为它总是可以被外部因素改变。