欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > StringBuilder 和 StringBuffer 的线程安全分析

StringBuilder 和 StringBuffer 的线程安全分析

2025/5/24 20:36:45 来源:https://blog.csdn.net/2401_86909484/article/details/148175575  浏览:    关键词:StringBuilder 和 StringBuffer 的线程安全分析

在 Java 编程中,字符串操作是非常常见的需求。Java 提供了两种主要的可变字符串类:StringBuilder 和 StringBuffer。虽然它们的 API 几乎完全相同,但在多线程环境下,它们的行为却有很大的不同。本文将深入探讨这两个类的线程安全性,并分析它们在不同场景下的适用情况。

一、StringBuilder 和 StringBuffer 的基本介绍

1. StringBuilder

StringBuilder 是 Java 5 中引入的,它是一个可变的字符序列。它提供了与 StringBuffer 兼容的 API,但不保证线程安全。在单线程环境下,StringBuilder 的性能要优于 StringBuffer。

2. StringBuffer

StringBuffer 是 Java 早期版本就存在的,它同样表示一个可变的字符序列。与 StringBuilder 不同的是,StringBuffer 是线程安全的,它的所有公共方法都被 synchronized 修饰,因此可以在多线程环境下安全使用。

二、线程安全的本质区别

1. StringBuffer 的线程安全实现

StringBuffer 的线程安全性是通过在方法级别使用 synchronized 关键字实现的。例如,它的 append () 方法的实现如下:

@Override
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

可以看到,append () 方法被 synchronized 修饰,这意味着在同一时刻,只有一个线程可以执行该方法。这种同步机制确保了多线程环境下的操作安全,但也带来了一定的性能开销。

2. StringBuilder 的非线程安全特性

StringBuilder 的方法没有使用 synchronized 修饰,因此它不是线程安全的。例如,它的 append () 方法实现如下:

@Override
public StringBuilder append(String str) {super.append(str);return this;
}

在多线程环境下,如果多个线程同时访问同一个 StringBuilder 实例并进行修改操作,可能会导致数据不一致或其他不可预期的结果。

三、线程安全的实际影响

1. 多线程环境下的问题示例

下面的代码演示了在多线程环境下使用 StringBuilder 可能出现的问题:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class StringSafetyDemo {private static StringBuilder builder = new StringBuilder();public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 1000; i++) {executor.submit(() -> {builder.append("a");});}executor.shutdown();while (!executor.isTerminated()) {Thread.sleep(100);}System.out.println("Builder length: " + builder.length());}
}

在这个示例中,我们创建了一个包含 10 个线程的线程池,每个线程向同一个 StringBuilder 实例追加字符 “a”。理论上,最终 StringBuilder 的长度应该是 1000,但实际上运行这段代码可能会得到不同的结果(一般得到的长度都小于1000),甚至可能抛出 StringIndexOutOfBoundsException 异常。

这里我们详细演示一下为什么会出现这种状况

StringBuilderappend()方法大致实现如下:

public StringBuilder append(String str) {// 1. 检查当前容量是否足够if (count + str.length() > value.length) {expandCapacity(count + str.length()); // 扩容操作}// 2. 将字符串复制到内部字符数组str.getChars(0, str.length(), value, count);// 3. 更新长度计数器count += str.length();return this;
}

假设有两个线程(T1 和 T2)同时调用append("a"),初始状态:

  • value数组长度为 10,已使用长度count = 8(即value[8]value[9]为空)
场景 1:两个线程同时检查容量
  1. T1 执行步骤 1:计算新长度8+1=9 ≤ 10,认为不需要扩容。
  2. T2 执行步骤 1:此时count仍为 8(T1 尚未更新),同样认为不需要扩容。
  3. T1 执行步骤 2:将'a'写入value[8]
  4. T2 执行步骤 2:将'a'写入value[8](覆盖 T1 写入的数据)。
  5. T1 执行步骤 3count变为 9。
  6. T2 执行步骤 3count变为 10(本应是 11,但 T2 的写入被覆盖)。

结果:两次append操作只增加了一个字符,最终长度为 10 而非 11。

场景 2:一个线程扩容时另一个线程写入
  1. T1 执行步骤 1:发现需要扩容,调用expandCapacity(9)创建新数组(长度 20)。
  2. T2 执行步骤 1:由于 T1 尚未更新value引用,T2 仍使用旧数组,写入value[8]
  3. T1 继续执行:将旧数组内容复制到新数组,但 T2 写入的value[8]未被复制。
  4. T1 更新value引用:T2 的写入丢失,且count可能被错误更新。

结果:数据丢失,最终长度可能小于预期。

2. 使用 StringBuffer 解决线程安全问题

如果将上面的代码中的 StringBuilder 替换为 StringBuffer,就可以确保结果的正确性:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class StringBufferThreadSafetyDemo {private static StringBuffer buffer = new StringBuffer();public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 1000; i++) {executor.submit(() -> {buffer.append("a");});}executor.shutdown();while (!executor.isTerminated()) {Thread.sleep(100);}System.out.println("Buffer length: " + buffer.length());}
}

无论运行多少次,这段代码都会输出 “Buffer length: 1000”,因为 StringBuffer 的方法是线程安全的。

四、性能比较

由于 StringBuffer 的方法是同步的,而 StringBuilder 的方法不是,因此在单线程环境下,StringBuilder 的性能要明显优于 StringBuffer。下面是一个简单的性能测试示例:

public class ATest {private static final int ITERATIONS = 100000;public static void main(String[] args) {// 测试StringBuilder性能long startTime = System.currentTimeMillis();StringBuilder sb = new StringBuilder();for (int i = 0; i < ITERATIONS; i++) {sb.append("test");}long endTime = System.currentTimeMillis();System.out.println("StringBuilder time: " + (endTime - startTime) + " ms");// 测试StringBuffer性能startTime = System.currentTimeMillis();StringBuffer sbf = new StringBuffer();for (int i = 0; i < ITERATIONS; i++) {sbf.append("test");}endTime = System.currentTimeMillis();System.out.println("StringBuffer time: " + (endTime - startTime) + " ms");}
}

在我的idea上运行这段代码,StringBuilder 的执行时间大约是 5 毫秒,而 StringBuffer 的执行时间大约是 15 毫秒。可以看到,StringBuilder 的性能优势非常明显。

五、适用场景

1. 使用 StringBuilder 的场景

  • 单线程环境下的字符串拼接操作
  • 需要高性能的字符串处理场景

2. 使用 StringBuffer 的场景

  • 多线程环境下的字符串拼接操作
  • 需要确保线程安全的字符串处理场景

六、总结

StringBuilder 和 StringBuffer 都是 Java 中用于处理可变字符串的类,但它们的线程安全性不同。StringBuffer 通过同步方法保证了线程安全,适用于多线程环境;而 StringBuilder 没有同步开销,性能更高,适用于单线程环境

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词