String、StringBuilder、StringBuffer的区别

String、StringBuilder、StringBuffer

String是常量,看看String类内部的变量就知道

private final byte[] value;//常量的byte,也就是在初始化时就已经固定了,不能再改
String a = "123";
a = a + "45";

执行上面这段代码时,实际上是在常量池中创建了“12345”,然后把变量a指向它。

如果在一个循环中频繁拼接,效率就会很低

在单线程下的执行效率

package string;

/**
 * @author: jane
 * @CreateTime: 2020/5/8
 * @Description: 测试单线程下,各自执行效率
 */
public class SingleThread {
    public static void main(String[] args) {
        StringCost();
        StringBuilderCost();
        StringBufferCost();
    }
    //each time,copy the string to other place.
    public static void StringCost(){
        long startTime = System.currentTimeMillis();
        String a = "";
        for(int i=0;i<100000;i++){
            a = a + i%10;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("String cost:"+(endTime-startTime));
    }
    //
    public static void StringBuilderCost(){
        long startTime = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<100000;i++){
            sb.append(i%10);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuilder cost:"+(endTime-startTime));
    }
    //
    public static void StringBufferCost(){
        long startTime = System.currentTimeMillis();
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<100000;i++){
            sb.append(i%10);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuffer cost:"+(endTime-startTime));
    }

}

输出

String cost:837
StringBuilder cost:0
StringBuffer cost:10

这样子看,貌似StringBuilder是最优的选择,确实,在单线程情况下,如果需要频繁拼接,使用StringBuilder更好

那在多线程下会有问题吗?

package string;

import java.util.concurrent.CountDownLatch;

/**
 * @author: jane
 * @CreateTime: 2020/5/8
 * @Description: 测试100个线程下的问题
 */
public class MultipleThread {

    public static void main(String[] args) {
        new Thread(MultipleThread::StringBuilder).start();
        new Thread(MultipleThread::StringBuffer).start();
    }
    //
    public static void StringBuilder(){
        StringBuilder sb = new StringBuilder();
        CountDownLatch builderLatch = new CountDownLatch(100);//类似于信号量,初始值为100
        for(int i=0;i<100;i++){
            new Thread(() -> {
                for(int i1 = 0; i1 <1000; i1++){
                    sb.append(i1 %10);
                }
                builderLatch.countDown();//这个线程完成了工作,将信号量减1
            }).start();
        }
        try {
            builderLatch.await();//如果值不为0,则会阻塞
            String s = sb.toString();
            System.out.println("StringBuilder length: "+s.length());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //
    public static void StringBuffer(){
        StringBuffer sb = new StringBuffer();
        CountDownLatch bufferLatch = new CountDownLatch(100);
        for(int i=0;i<100;i++){
            new Thread(() -> {
                for(int i1 = 0; i1 <1000; i1++){
                    sb.append(i1 %10);
                }
                bufferLatch.countDown();
            }).start();
        }
        try {
            bufferLatch.await();
            String s = sb.toString();
            System.out.println("StringBuffer length: "+s.length());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面还用到了CountDownLatch,主要是用来阻塞主线程,保证100个线程都执行完毕再输出长度;

输出

StringBuilder length: 9156
StringBuffer length: 10000

StringBuilder出现了线程不安全的情况

很清楚,StringBuffer是StringBuilder在多线程下的替换,尽管速度会慢些,但能保证线程安全


看看两个的源代码

image-20200508160609788

image-20200508160623719

就是加了一个synchronized的同步关键字,然后调用父类的append方法,父类的append如下

image-20200508160741050

这个父类是没有同步的;

在StringBuilder中,由于没有用synchronized,可能出现两个线程同时进入到这个方法;

最后导致覆盖的情况(线程一读取到count之后,切换到线程二,也读取count。此时两个线程获取到相同的count,从同一位置写入数据)

有时候可能还会出现ArrayIndexOutOfBoundsException异常,不过我测试了好几次,都没发生。。。

这种情况和解析可以参考这篇博客

posted @ 2020-05-08 16:38  木灬匕禾页  阅读(71)  评论(0)    收藏  举报