20250214 随笔 线程安全 线程不安全
1. 什么是线程安全 & 线程不安全?
- 线程安全(Thread-Safe):在多线程环境下访问同一个对象时,不会产生数据竞争、不会出现数据不一致的问题。
- 线程不安全(Not Thread-Safe):在多线程环境下,多个线程同时访问同一个对象可能会导致数据不一致,需要手动加锁或者使用线程安全的方式来操作。
2. StringBuffer 线程安全,StringBuilder 线程不安全
💡 主要区别在于:
StringBuffer
是线程安全的,因为它的很多方法都加了synchronized
关键字。StringBuilder
是线程不安全的,因为它没有加synchronized
,多线程访问时可能会出现数据不一致问题。
🔹 StringBuffer(线程安全)
StringBuffer sb = new StringBuffer("Hello");
// 多线程修改同一个 StringBuffer 对象
Thread t1 = new Thread(() -> {
sb.append(" World");
});
Thread t2 = new Thread(() -> {
sb.append(" Java");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sb); // 结果是可预测的,因为 append() 方法是同步的
🔹 StringBuffer
内部的方法(如 append()
、insert()
)都加了 synchronized
,保证了 同一时刻只有一个线程可以修改 StringBuffer
对象,所以它是 线程安全的。
🔹 StringBuilder(线程不安全)
StringBuilder sb = new StringBuilder("Hello");
// 多线程修改 StringBuilder
Thread t1 = new Thread(() -> {
sb.append(" World");
});
Thread t2 = new Thread(() -> {
sb.append(" Java");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sb); // 可能输出 Hello World Java,也可能出现乱码
🔹 StringBuilder
没有 synchronized
,多个线程同时访问时,可能会发生数据错乱,如:
Hello WorldJava
Hello JavaWorld
Hello WJavorld
💡 因为多个线程可以同时修改 StringBuilder
,导致数据写入时发生竞争,出现不可预测的情况。
3. StringBuffer & StringBuilder 内部实现区别
📌 StringBuffer 使用 synchronized
,保证线程安全
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
🔹 append()
方法使用了 synchronized
,意味着同一时刻只有一个线程能执行它,这样就保证了线程安全。
📌 StringBuilder 没有 synchronized
,线程不安全
public StringBuilder append(String str) {
super.append(str);
return this;
}
🔹 append()
方法没有 synchronized
,多个线程同时修改时会产生数据竞争。
4. 线程安全 vs 线程不安全,如何选择?
特性 | StringBuffer(线程安全) | StringBuilder(线程不安全) |
---|---|---|
是否线程安全 | ✅ 是 | ❌ 不是 |
是否使用 synchronized | ✅ 是(同步) | ❌ 不是(无锁) |
适用场景 | 多线程环境(如 Web 服务器、并发任务) | 单线程环境(普通字符串拼接) |
性能 | 较慢(加锁有性能损耗) | 更快(无锁操作) |
替代方案 | StringBuilder + synchronized 或 Lock | 适用于单线程,性能更好 |
💡 如何选择?
- 单线程:使用
StringBuilder
,性能更高。 - 多线程:使用
StringBuffer
,防止数据竞争。 - 多线程但性能要求高?可以使用
StringBuilder
+synchronized
或Lock
。
5. 线程安全一定好?为什么有线程不安全的 StringBuilder?
💡 线程安全的代价是“性能下降”:
StringBuffer
每次操作都需要synchronized
,即使单线程也会加锁,影响性能。StringBuilder
不加锁,在单线程下性能更快,适合大多数情况。
示例:StringBuffer
在单线程下的性能劣势
long startTime = System.nanoTime();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 100000; i++) {
sb.append("Java");
}
long endTime = System.nanoTime();
System.out.println("StringBuffer 时间:" + (endTime - startTime) + " ns");
对比:StringBuilder
在单线程下的性能
long startTime = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("Java");
}
long endTime = System.nanoTime();
System.out.println("StringBuilder 时间:" + (endTime - startTime) + " ns");
💡 单线程下,StringBuilder
速度明显更快,因为它没有加锁!
6. 线程安全 & 线程不安全的选择策略
场景 | 推荐选择 |
---|---|
单线程字符串拼接 | ✅ StringBuilder |
多线程共享字符串对象 | ✅ StringBuffer |
多线程 + 高性能要求 | ✅ StringBuilder + synchronized |
不可变字符串(不修改) | ✅ String |
7. 结论
✅ StringBuffer
是线程安全的,因为它的方法加了 synchronized
,适用于多线程环境。
✅ StringBuilder
是线程不安全的,但性能更好,适用于单线程环境。
✅ 线程安全不一定好,StringBuffer
虽然安全,但单线程下性能比 StringBuilder
差。
✅ 如果多线程但又想提升性能,可以使用 StringBuilder
+ synchronized
进行手动控制。
💡 面试高频考点:
- StringBuffer 和 StringBuilder 的区别?
- 为什么 StringBuilder 是线程不安全的?
- 如何让 StringBuilder 变成线程安全的? ✅ 答案:加
synchronized
,或者使用Lock
- 什么时候用 StringBuffer?什么时候用 StringBuilder? ✅ 单线程用
StringBuilder
,多线程用StringBuffer