raw*_*ata 2 java multithreading thread-safety
字符串是不可变的,这意味着一旦您修改该值,它就会创建一个新的引用并保留先前的引用值不变。
但是,当有人争论时我不明白:
字符串是线程安全的,因为它们是不可变的
考虑下面的代码:
private String str = "1";
ExecutorService executorService = Executors.newFixedThreadPool(10);
IntStream.range(0, 1000).forEach((i)-> executorService.submit(()-> {
str = str +"1";
}));
executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(str.length());
Run Code Online (Sandbox Code Playgroud)
如果它是线程安全的,那么它应该打印,1001而它总是打印小于预期值。
我知道上面的代码将创建1001不可变的引用,每个引用本身都是线程安全的,但作为开发人员,仍然不能使用不可变的东西并期望它们end-result是线程安全的。
恕我直言,不变性不能保证线程安全。
有人可以向我解释一下字符串如何保证线程安全吗?
更新:
感谢您的回答,我知道每个字符串都可以是线程安全的,但我的观点是,当您在其他方法中使用它们时,线程安全性和不变性之间没有直接关系。
例如,不可变对象可以在有状态对象中使用,并以非线程安全结果结束,可变对象也可以在同步方法中使用,并以线程安全结果结束。
了解编程语言中的内存如何工作非常重要。变量str并不像您想象的那样是字符串对象。但它是对带有某个地址的字符串对象的引用。
修改str指向的内容,不会修改它指向的字符串。事实上发生的事情是这样的:
我们有一个内存池,在我们的池中是三个字符串。每个字符串都有一个地址,可以让我们找到它。
我们有一个变量指向每一个,我们将它们称为 a、b 和 c。
如果我们说:System.out.println(a);Java 将打印Hello. 但a不是“Hello”。相反, a 是包含0x449345的内容。然后计算机会说:“好吧,我去把 0x449345 处的内容打印出来。” 当它查看该地址时,它找到了字符串“Hello”。
但是,如果您说:a = "NEW STRING";a 不会指向我们以前的任何地址。相反,会创建一个新地址,并将“NEW STRING”放置在该内存位置内。
这也是 Java 中垃圾收集的工作原理。一旦您将 a 设置为等于“NEW STRING”,它就不再指向 0x449345,这告诉垃圾收集器该对象可以安全删除。这就是您的程序自行清理的方式,并且不会消耗大量内存。
因此,指向字符串的引用不是线程安全的,但实际对象是安全的!任何不可变对象都是安全的,因为您根本无法修改该对象,您只能修改变量指向的内容。您必须完全指向一个不同的对象才能“修改”您的不可变对象。
我想这可以概括为以下几点:
String是线程安全的。(它们是线程安全的,因为 String对象是不可变的,但原因与您的示例没有直接关系。)final你的例子确实如此str = str + 1;。它将String对象上的操作与非同步共享变量 ( str) 上的操作结合起来。由于后者,它不是线程安全的。
1 - 更准确地说,在写入和读取之间的关系之前不发生操作以保证所需的内存可见性属性,并且不存在锁定以保证所需的原子性属性。(“必需”是指算法正确性所必需的......)
2 - 共享意味着多个线程可见并被多个线程使用。如果一个变量仅对一个线程可见或由一个线程使用,则称该变量是线程限制的并且实际上不被共享。