字符串线程安全吗?

字符串是不可变的,这意味着一旦您修改了值,它就会创建一个新的引用并保持之前的引用值不变。


但是,当有人争论时,我不明白:


字符串是线程安全的,因为它们是不可变的


考虑下面的代码:


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

如果它是线程安全的,那么它应该打印1001,而它总是打印小于预期值。


我知道上面的代码将创建1001不可变的引用,每个引用本身都是线程安全的,但作为开发人员,仍然不能使用不可变的东西并期望它end-result是线程安全的。


恕我直言,不变性不能保证线程安全。


有人可以向我解释一下 String 是如何线程安全的吗?


更新:


感谢您的回答,我知道每个字符串都可以是线程安全的,但我的观点是,当您在其他方法中使用它们时,线程安全性和不变性之间没有直接关系。


例如,不可变对象可以在有状态对象中使用并以非线程安全结果结束,而可变对象也可以在同步方法中使用并以线程安全结果结束。


喵喵时光机
浏览 283回答 3
3回答

largeQ

我认为可以总结如下:对String对象的操作是线程安全的。(它们是线程安全的,因为 String对象是不可变的,但为什么与您的示例没有直接关系。)非共享变量2上的非同步读取和写入操作1不是线程安全的,无论变量的类型如何。final你的例子确实如此str = str + 1;。它将String对象操作与非同步共享变量 ( str) 的操作结合起来。由于后者,它不是线程安全的。1 - 更准确地说,在写入和读取之间没有发生关系的操作以保证所需的内存可见性属性,并且没有锁定来保证所需的原子性属性。(“必需”是指算法正确性所必需的......)2 - 共享意味着对多个线程可见和使用。如果一个变量只对一个线程可见或被一个线程使用,则称它是线程受限的,实际上它是不共享的。

繁星淼淼

了解内存在编程语言中的工作原理很重要。变量str不是一个字符串对象,就像你想的那样。但它是对带有一些地址的字符串对象的引用。修改str指向的内容,不会修改它指向的字符串。事实上发生的事情是这样的:我们有一个内存池,在我们的池中是三个字符串。每个字符串都有一个地址,可以让我们找到它。字符串 1 - “你好”,地址:0x449345字符串 2 - “那里”,地址:0x058345字符串 3 - “世界”,地址:0x004934我们有一个指向每个变量的变量,我们称它们为 a、b 和 c。如果我们说:System.out.println(a);Java 会打印Hello. 但是a不是"Hello"。相反, a 是包含0x449345的东西。电脑接着说:“好的,我去把 0x449345 的内容打印出来。” 当它查看该地址时,它会找到字符串“Hello”。但是,如果您说:a = "NEW STRING";a 不会指向我们以前的任何地址。而是创建一个新地址,并将“新字符串”放置在该内存位置内。这也是垃圾收集在 Java 中的工作方式。一旦你设置一个等于“NEW STRING”的值,它就不再指向 0x449345,这告诉垃圾收集器该对象可以安全删除。这就是您的程序自行清理的方式,并且不会占用大量 RAM。因此,指向字符串的引用不是线程安全的,而是实际的对象!任何不可变对象都是安全的,因为您根本无法修改该对象,您只能修改变量指向的内容。您必须完全指向一个不同的对象才能“修改”您的不可变对象。

哆啦的时光机

您的str引用不是一成不变的,每次重新分配它的值时都会对其进行变异。由于您在没有同步或互斥锁的线程之间共享可变状态,因此结果不安全。以下为我尝试了 5 次。注意我在连接你的字符串时添加了互斥锁。public class QuickyTest {   private static String str = "1";   public static void main( String[] args ) throws Exception {      ExecutorService executorService = Executors.newFixedThreadPool( 10 );      IntStream.range( 0, 1000 ).forEach( ( i ) -> executorService.submit( () -> {         append( "1" );      }      ) );      executorService.awaitTermination( 10, TimeUnit.SECONDS );      System.out.println( str.length() );      executorService.shutdown();   }   private static synchronized void append( String s ) {      str = str + s;   }}总是打印“1001”。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java