分享

面试官:连StringBuilder线程是否安全都说不出个所以然,怎么写出高效稳定的程序

 爱开发 2022-08-02 发布于广东

程序员面试时,线程安全知识点经常会遇到。

小洪是一名java程序员,工作两年多了,最近去了某互联网公司面试,面试时,面试官问了这样一个问题:

面试官:StringBuilder和StringBuffer有什么区别?

小洪:StringBuilder线程不安全的,StringBuffer是线程安全的

面试官:能举个例子说说StringBuilder是怎么线程不安全的吗?

小洪:……

小洪有点懵了,平时在用在使用StringBuilder追加字符时,只是简单的用,只知道它是不是线程安全,至于不安全在哪里,小洪并没有去关注。

见小洪没有作答,面谈时面试官又问,StringBuffer哪里安全了,小洪也回答不出个所以然。

面试官:你都工作两年了,这个都不知道,怎么写出稳定的程序啊?小洪一脸的无助,不过确实不应该,类似StringBuilder常见的知识点,平时应该不会少用,应该掌握才对。

刚毕业的时候,面试时也遇到了类似关于StringBuild的问题,关于StringBuild线程不安全简单来说StringBuilder没有加锁,而StringBuffer有加锁。面试时,这样回答固然没有错,但要是能进一步分析个所以然那就更好了,相信能给面试官留下更好的印象。

我们举个例子来验证下

1. 分别用500个线程写StringBuffer和StringBuilder,

2. 保证在各自500个线程执行完后打印StringBuffer和StringBuilder字符串长度

3. 检验运行结果。

测试

 1  public static void main(String[] args) {
2    StringBuilder stringBuilder = new StringBuilder();
3    class MyRunnable implements Runnable{
4        @Override
5        public void run() {
6            stringBuilder.append("p");
7        }
8    }
9    ExecutorService exec = Executors.newCachedThreadPool();
10    for(int i=0;i<500;i++) {
11        exec.execute(new MyRunnable());
12    }
13    exec.shutdown();
14    System.out.println(stringBuilder.length());
15}

我们创建了500个线程,每个线程往StringBuilder对象里面append一个字符p。正常情况下代码应该输出500,但是实际运行结果是什么呢?

测试结果

我们看到字符串长度有时候是496,有时候是498,小于预期值500。而当我们把StringBuilder换成StringBuffer不管运行多少次,结果的值都是500,从这个例子我们可以得出StringBuffer线程安全,StringBuilder线程不安全得到了证明。

为什么输出值跟预期值不一样,源码分析

为什么会小于期望值,我们来看下StringBuilder的源码

StringBuilder的append()方法调用的父类AbstractStringBuilder的append()方法,我们来看下这个方法具体做了什么。

1public AbstractStringBuilder append(String str{
2    if (str == null)
3        return appendNull();
4    int len = str.length();
5    ensureCapacityInternal(count + len);
6    str.getChars(0, len, value, count);
7    count += len;
8    return this;
9}

细心的你会发现count += len;这一行代码并不是一个原子操作。

假设count的值为10,len值为1,这时候有两个线程同时执行到了这行代码,count值都是10,执行完后将结果赋值给count,这两个线程执行完后count值为11,而不是12。这就是为什么测试代码输出的值要小于我们的期望值500的原因了。

而当我们换成StringBuffer类是,为什么能保证输出的是500,我们看一下StringBuffer源码就会发现所有写操作都有synchronized修饰了

而StringBuilder的写操作则没有使用synchronized进行修饰。

StringBuilder类的append方法

1@Override
2public StringBuilder append(String str) {
3   super.append(str);
4   return this;
5 }

StringBuffer 类的append方法

1    @Override
2    public synchronized StringBuffer append(String str) {
3     toStringCache = null;
4     super.append(str);
5     return this;
6   }

关于StringBuilder不是线程安全的例子就分析到这。笔者觉得类似这样的基础知识点需要在平时学习中不断摸索,不断去探索实践,不断运用,才能在工作中更好地解决问题。由于笔者水平有限,文中纰漏之处在所难免,权当抛砖引玉,不妥之处,敬请读者不吝赐教,是为至盼。

作者:洪生鹏 

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多