StringBuilder线程安全的探究

134 阅读1分钟

嘿,各位码农小伙伴们,你们好,我是仨仨!

今天我们要探讨一个比较热门的话题——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变得线程安全。有几种方式可以实现:

  1. 在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();
  1. 在线程中使用同步块,对于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");  
        }  
    }  
};
  1. 对每个线程定义单独的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);  
};
  1. 使用线程安全的StringBuffer,简单粗暴,这也是最简单的实现方式,前面的代码中也有展示,这里就不再赘述。

这样,通过一些简单的方式,我们就能够让StringBuilder在多线程中变得更加安全。希望这篇文章能够帮助你更好地理解StringBuilder的线程安全问题和解决方案。

有其他问题,可以在我的主页找的我的联系方式向我提问。再见啦!