StringBuilder线程安全探讨
StringBuilder在多线程中可能会出现竞争
在多线程中,由于StringBuilder的方法不是同步的,所有线程都可以自由地访问并更改StringBuilder对象。
我们以一个简单的例子证明StringBuilder在多线程中不是线程安全的,我们通过将StringBuilder与线程安全的StringBuffer在多线程条件下执行同一个方法对比:
定义StringBuilder 与 StringBuffer
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
模拟多线程资源竞争的情景
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
stringBuilder.append("a");
stringBuffer.append("a");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
stringBuilder.append("b");
stringBuffer.append("b");
}
}
});
输出结果
System.out.println("StringBuilder Result: " + stringBuilder.toString());
System.out.println("StringBuffer Result: " + stringBuffer.toString());
完整代码
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
stringBuilder.append("a");
stringBuffer.append("a");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
stringBuilder.append("b");
stringBuffer.append("b");
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("StringBuilder Result: " + stringBuilder.toString());
System.out.println("StringBuffer Result: " + stringBuffer.toString());
输出结果:
StringBuilder Result: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba aaaaaaabbbababababababababababab babababababababababababababababababababababababababababababababababababb abababb ba bababa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
StringBuffer Result: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabababababababababababababababababababababababababababababababababababababababababababababababababababababababbabbabababababbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
多次执行代码后,从输出结果来看,这两个字符串的长度相同。StringBuilder的输出结果中,多次出现资源丢失的情况,两个线程存在资源竞争,而StringBuffer的输出结果中则没有出现该情况。
StringBuilder的线程安全的实现形式
StringBuilder的线程安全的实现形式,有以下几种方式:
- 在StringBuilder的公共方法前添加synchronized关键字,定义如下的SafeStringBuilder
public class SafeStringBuilder {
private StringBuilder stringBuilder = new StringBuilder();
public synchronized void append(String str) {
stringBuilder.append(str);
}
public synchronized void insert(int offset, String str) {
stringBuilder.insert(offset, str);
}
public synchronized void delete(int start, int end) {
stringBuilder.delete(start, end);
}
public synchronized void replace(int start, int end, String str) {
stringBuilder.replace(start, end, str);
}
public synchronized void reverse() {
stringBuilder.reverse();
}
public synchronized String toString() {
return stringBuilder.toString();
}
}
使用SafeStringBuilder代替StringBuilder
SafeStringBuilder sb = new SafeStringBuilder();
- 在线程中使用同步块,对于A、B线程,在循环前使用了synchronized关键字,以实现线程安全
Runnable appendA = () -> {
synchronized (stringBuilder) {
for (int i = 0; i < 100; i++) {
stringBuilder.append("a");
}
}
};
Runnable appendB = () -> {
synchronized (stringBuilder) {
for (int i = 0; i < 100; i++) {
stringBuilder.append("b");
}
}
};
- 对每个线程定义单独的StringBuilder,以下方法中,对于A、B线程单独定义了一个StringBuilder对象
StringBuilder sb = new StringBuilder();
Runnable appendA = () -> {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 100; i++) {
stringBuilder.append("a");
}
sb.append(stringBuilder);
};
Runnable appendB = () -> {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 100; i++) {
stringBuilder.append("b");
}
sb.append(stringBuilder);
};
- 使用线程安全的StringBuffer,使用StringBuffer是实现StringBuilder线程安全最简单的实现形式,在前面的代码中也有展示,不过多赘述。