精华内容
下载资源
问答
  • 为什么StringBuffer线程安全

    万次阅读 2019-05-21 21:15:56
    StringBuffer 和 StringBuilder 最大的区别在于: 参考: ... 线程是否安全 性能 推荐使用场景 ...StringBuffer 线程安全 低 多线程环境 StringBuilder 非线程安全 高 单线程环境 主要因为StringBuffer...

    参考:
    https://blog.csdn.net/u014705854/article/details/80815102

    StringBuffer 和 StringBuilder 最大的区别在于:

    线程是否安全 性能 推荐使用场景
    StringBuffer 线程安全 多线程环境
    StringBuilder 非线程安全 单线程环境

    主要因为StringBuffer很多方法都是synchronized 修饰的

     public StringBuffer(CharSequence seq) {
            this(seq.length() + 16);
            append(seq);
        }
     
        public synchronized int length() {
        return count;
        }
     
        public synchronized int capacity() {
        return value.length;
        }
    

    而线程安全的优势就是他可以在多线程环境下使用。多线程不要用StringBuilder,否则会出现问题。

    package bingfa;
     
    /**
     * StringBuffer 线程安全  
     * StringBuilder 线程非安全
     * @author 909974
     *
     */
    public class Thread4 {
        public static void main(String[] argaa) {
            MyString sb = new MyString();
            StringBuilder sbBuilder = new StringBuilder();
            StringBuffer stringBuffer = new StringBuffer();
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 1000; j++) {
                            sb.append(1);
                            sbBuilder.append("1");
                            stringBuffer.append("1");
                            System.out.println(sb.getNum() + "-" + sbBuilder.length() + "-" + stringBuffer.length());
                        }
    	        }
    	    }).start();
    	}
        }
    }
     
     
    class MyString {
        private Integer num = 0;
     
        public Integer getNum() {
            return num;
        }
     
        public void setNum(Integer num) {
            this.num = num;
        }
    	
        public synchronized void append(Integer num) {
    	this.num = this.num + num;
        }
    }
    
    

    结果
    在这里插入图片描述
    上面的代码中,用了自己定义的MyString类与StringBuffer以及StringBuilder。启用了1000个线程,每个线程都进行“累加”操作,并打印结果。

    结果显示,StringBuffer是正确答案,StringBuilder少于正确答案,而MyString,会根据append方法是否加上synchronized关键字而显示不同结果。

    所以StringBuilder不能用于对同一对象的多线程操作。

    不过一般对字符串的操作,并不会用到多线程,所以绝大多数时候,用StringBuilder即可。

    展开全文
  • 为什么StringBuilder是线程不安全的 为什么StringBuffer线程安全

    为什么StringBuilder是线程不安全的

    1、案例测试
    通过一个案例看一下效果,执行方法如下:

    public static void testBuilder() {
    	StringBuilder builder = new StringBuilder();
    	for(int i = 0; i < 10; i++) {
    		 new Thread(new Runnable() {
    	            @Override
    	            public void run() {
    	            	for(int j = 0; j < 1000; j++) {		            		
    	            		builder.append("a");
    	            	}
    	            }
    	        }).start();
    	}
    	try {
    		Thread.sleep(100);
    	} catch (InterruptedException e) {
    		e.printStackTrace();
    	}
    	System.out.print("线程不安全:"+builder.length());
    }
    

    这个方法需求是创建10个线程,每个线程内执行1000次循环,在循环内拼接字符串a,然后再睡100毫秒,最后打印出拼接结果的长度,按正常的输出思路,应该是打印出的长度是10000,但实际结果却不是10000,执行结果如下所示,先报了一个异常,然后执行结果的长度是8554,而不是10000,下面来分析一下结果为什么会这样。

    Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException
    	at java.lang.System.arraycopy(Native Method)
    	at java.lang.String.getChars(Unknown Source)
    	at java.lang.AbstractStringBuilder.append(Unknown Source)
    	at java.lang.StringBuilder.append(Unknown Source)
    	at cn.com.agree.TestString$2.run(TestString.java:33)
    	at java.lang.Thread.run(Unknown Source)
    线程不安全:8554
    

    2、结果分析

    第一个问题:为什么长度小于10000?
    builder.append("a");的append方法点进源码查看如下:

    @Override
      public StringBuilder append(String str) {
          super.append(str);
          return this;
      }
    

    StringBuilder类继承父类AbstractStringBuilder,由源码得知StringBuilderappend方法继承父类的append方法,再点进super.append(str);查看父类方法的实现:

        public AbstractStringBuilder append(String str) {
           if (str == null)
               return appendNull();
           int len = str.length();
           ensureCapacityInternal(count + len);
           str.getChars(0, len, value, count);
           count += len;
           return this;
       }
    

    父类的append方法中有一行代码:

     count += len;
    

    这一行代码不是原子操作,(不知道什么是原子操作的请查看:https://blog.csdn.net/u010982507/article/details/102651784),
    所以当多个进程同时执行count+=len的时候,count的值只会加一次,而不是多次,所以执行后的结果会小于预期结果。

    第二个问题:为什么会报java.lang.ArrayIndexOutOfBoundsException异常?
    java.lang.ArrayIndexOutOfBoundsException是数组角标越界异常,查看源码:

    public AbstractStringBuilder append(String str) {
         if (str == null)
             return appendNull();
         int len = str.length();
         ensureCapacityInternal(count + len);
         str.getChars(0, len, value, count);
         count += len;
         return this;
     }
    

    其中ensureCapacityInternal(count + len);方法是一个自动扩容的操作,点进源码查看:

       private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0)
                expandCapacity(minimumCapacity);
        }
    

    源码中执行的是expandCapacity(minimumCapacity);方法,再点进去查看:

    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
    

    这个方法是判断原来的value字符数组的容量是否能放进新的字符,如果放不下就将value的长度乘以2再加2,再将新的数组复制给原数组,其中value是一个字符数组全局变量char[] value;

    回归正题,当两个线程同时进行到 ensureCapacityInternal(count + len);方法的时候,第一个线程执行完这个方法,cpu的时间片正好用完(注:时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。)。
    第一个线程的时间片用完时,count的长度是100,cpu执行第二个线程,第二个线程执行完后,count长度变为101,那么cpu又分时间片给第一个线程,第一个进程执行str.getChars(0, len, value, count);(这行代码执行的是将str字符串中每个字符复制给value,开始位置是0,结束位置是len,总共count个)程序时,count由原来的100变为101, 所以复制第101个字符的时候,len长度不够,就会发生数组角标越界异常。

    为什么StringBuffer是线程安全的

    1、案例测试

    public static void testBuffer() {
    	StringBuffer buffer = new StringBuffer();
    	for(int i = 0; i < 10; i++) {
    		 new Thread(new Runnable() {
    	            @Override
    	            public void run() {
    	            	for(int j = 0; j < 1000; j++) {		            		
    	            		buffer.append("a");
    	            	}
    	            }
    	        }).start();
    	}
    	try {
    		Thread.sleep(100);
    	} catch (InterruptedException e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    	System.out.print("线程安全:" + buffer.length());
    }
    

    2、执行结果

    线程安全:10000
    

    执行结果与预期结果一致,那么为什么StringBuffer就能线程安全呢?点进StringBufferappend方法查看源码:

     @Override
     public synchronized StringBuffer append(String str) {
         toStringCache = null;
         super.append(str);
         return this;
     }
    

    方法上加了一个synchronized锁,所以保证了线程的安全性,StringBuffer的性能也比StringBuilder低。

    java9中改进

    Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,因此字符串的每个字符占2字节;而Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,因此字符串的每个字符只占1字节。所以Java9的字符串更加节省空间,字符串的功能方法也没有受到影响。
    摘自:https://blog.csdn.net/csxypr/article/details/92378336

    展开全文
  • 我们非常自信的说出:StringBuilder是线程不安全的,StirngBuffer是线程安全的 面试官:StringBuilder不安全的点在哪儿? 这时候估计就哑巴了。。。 分析 StringBuffer和StringBuilder的实现内部是和String内部一样...

    面试中经常问到的一个问题:StringBuilder和StringBuffer的区别是什么
    我们非常自信的说出:StringBuilder是线程不安全的,StirngBuffer是线程安全的
    面试官:StringBuilder不安全的点在哪儿?
    这时候估计就哑巴了。。。

    分析
    StringBuffer和StringBuilder的实现内部是和String内部一样的,都是通过 char[]数组的方式;不同的是String的char[]数组是通过final关键字修饰的是不可变的,而StringBuffer和StringBuilder的char[]数组是可变的。

    首先我们看下边这个例子:

    Copy
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 10000; i++){
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++){
                        stringBuilder.append("a");
                    }
                }).start();
            }
    
            Thread.sleep(100L);
            System.out.println(stringBuilder.length());
        }
    }
    

    直觉告诉我们输出结果应该是10000000,但是实际运行结果并非我们所想。

    执行结果
    在这里插入图片描述

    从上图可以看到输出结果是9970698,并非是我们预期的1000000,并且还抛出了一个异常ArrayIndexOutOfBoundsException{非必现}

    为什么输出结果并非预期值?
    我们先看一下StringBuilder的两个成员变量(这两个成员变量实际上是定义在AbstractStringBuilder里面的,StringBuilder和StringBuffer都继承了AbstractStringBuilder)
    在这里插入图片描述

    AbstractStringBuilder.class

    StringBuilder的append方法
    在这里插入图片描述

    StringBuilder.append(String str)

    StringBuilder的append方法调用了父类的append方法
    在这里插入图片描述

    AbstractStringBuilder.append(String str)

    我们直接看第七行代码,count += len; 不是一个原子操作,实际执行流程为

    首先加载count的值到寄存器
    在寄存器中执行 +1操作
    将结果写入内存

    假设我们count的值是10,len的值为1,两个线程同时执行到了第七行,拿到的值都是10,执行完加法运算后将结果赋值给count,所以两个线程最终得到的结果都是11,而不是12,这就是最终结果小于我们预期结果的原因。

    为什么会抛出ArrayIndexOutOfBoundsException异常?
    我们看回AbstractStringBuilder的追加()方法源码的第五行,ensureCapacityInternal()方法是检查StringBuilder的对象的原字符数组的容量能不能盛下新的字符串,如果盛不下就调用expandCapacity()方法对字符数组进行扩容。

    private  void  ensureCapacityInternal(int  minimumCapacity)  {
             //溢出意识代码
        if  (minimumCapacity  -  value .length>  0)
            expandCapacity(minimumCapacity); 
    }
    

    扩容的逻辑就是新一个新的字符数组,新的字符数组的容量是原来字符数组的两倍再加2,再通过System.arryCopy()函数将原数组的内容复制到新数组,最后将指针指向新的字符数组。

    void  expandCapacity(int  minimumCapacity)  {
         //计算新的容量
        int  newCapacity =  value .length *  2  +  2 ; 
        //中间省略了一些检查逻辑
         ...
         value  = Arrays.copyOf( value,newCapacity); 
    }
    

    Arrys.copyOf()方法

    public  static  char []  copyOf(char [] original,  int  newLength)  {
         char [] copy =  new  char [newLength]; 
        //拷贝数组
         System.arraycopy(original,  0,copy,  0,
                             Math.min(original.length,newLength)); 
        返回  副本; 
    }
    

    AbstractStringBuilder的追加()方法源码的第六行,是将字符串对象里面字符数组里面的内容拷贝到StringBuilder的对象的字符数组里面,代码如下:

    str.getChars(0,len,  value,count);
    

    则GetChars()方法

    public  void  getChars(int  srcBegin,  int  srcEnd,  char  dst []int  dstBegin)  {
         //中间省略了一些检查
         ...   
        System.arraycopy( value,srcBegin,dst,dstBegin,srcEnd  -  srcBegin); 
    }
    

    拷贝流程见下图
    StringBuilder.append()执行流程
    在这里插入图片描述

    假设现在有两个线程同时执行了StringBuilder的append()方法,两个线程都执行完了第五行的ensureCapacityInternal()方法,此刻count=5
    在这里插入图片描述

    StringBuilder.append()执行流程2

    这个时候线程1的cpu时间片用完了,线程2继续执行。线程2执行完整个append()方法后count变成6了。
    在这里插入图片描述

    StringBuilder.append()执行流程3

    线程1继续执行第六行的str.getChars()方法的时候拿到的count值就是6了,执行char[]数组拷贝的时候就会抛出ArrayIndexOutOfBoundsException异常。

    至此,StringBuilder为什么不安全已经分析完了。如果我们将测试代码的StringBuilder对象换成StringBuffer对象会输出什么呢?
    在这里插入图片描述

    StringBuffer输出结果

    结果肯定是会输出 1000000,至于StringBuffer是通过什么手段实现线程安全的呢?看下源代码就明白了了。。。
    StringBuffer.append()
    在这里插入图片描述

    展开全文
  • 其中比较特殊的就是StringBuffer了,它是线程安全的,那为什么说它是线程安全的呢? StringBuffer sb=new StringBuffer(); new一个StringBuffer,然后 sb.append("大家好啊!"); 进入到源码中: 第一行中: s...

    java中操作字符串的类有:String / Stringbuild / StringBuffer

    其中比较特殊的就是StringBuffer了,它是线程安全的,那为什么说它是线程安全的呢?

    StringBuffer sb=new StringBuffer();

    new一个StringBuffer,然后

    sb.append("大家好啊!");

    进入到源码中:

    第一行中:

    super(str.length() + 16);

     只是为了创建存放字节的容器,这个我们不用管,主要是第二行:

    找到它的append()方法:

    在这里我们能够看到synchronized关键字对 append进行加锁同步这样就实现了,线程安全.

    展开全文
  • //通过反转字符串来验证StringBuffer线程安全而StringBuilder是线程不安全的 public class Practice { public static void main(String[] args) { /* * 声明个字符串s,用下划线和井号是因为两个比较好区分...
  • 二话不说先上代码: public class TestDemo { public static void main(String args[]) throws Exception{ ...那么两个线程执行结束之后最终stringBuffer的长度每次都是200,大家可以自行验证。
  • 你还以为StringBuffer线程安全?面试问到再也别说一定安全了。 每一个学过java的小伙伴都会背,StringBuffer线程安全的,StringBuilder是非线程安全的;Hashtable是线程安全的,HashMap是非线程安全的。把这几...
  • 我们都知道StringBuffer线程安全,而StringBuilder不是线程安全的(原因大家肯定也知道,StringBuffer中的方法都加了synchronized关键字)。所以网上很多资料都说,多线程不要用StringBuilder,否则会出现问题。...
  • StringBuffer属于线程安全,相对重量级 StringBuilder属于非线程安全,相对轻量级 线程安全的概念: 网络编程中许多线程可能会同时运行一段代码。当每次运行结果和单独线程运行的结果是一样的,叫做线程安全。 ...
  • StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final... 关于线程安全问题就要看他们的jdk源码了。 1. StringBuilder 我们直达app...
  • 证明StringBuffer线程安全,StringBuilder线程不安全证明StringBuffer线程安全StringBuilder线程不安全 不多说直接列代码 解释 结果 源码分析不多说直接列代码 @Test public void testStringBuilderAndStringBuffer...
  • http://www.w3c.com.cn/%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3stringbuffer%E6%98%AF%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84stringbuilder%E6%98%AF%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8%E7%9A%84
  • 序言 StringBuffer与StringBuilder是java.lang包下被大家熟知的两个类。 其异同: 一、长度都是可扩充的;...二、StringBuffer线程安全的,StringBuilder是线程不安全的。 那么他们的长度是如何实现动
  • @[TOC]String StringBuilder StringBuffer 速度 线程安全性验证
  • StringBuilder是线程不安全的,执行效率高 StringBuffer线程安全的,加了synchronized同步关键字,执行效率高
  • StringBuilder和StringBuffer都继承了AbstractStringBuilder,AbstractStringBuilder内有两个非常重要的变量,分别是: // 用于存储字符的容器 char[] value; // 已存储的字符数量 int count; 注意,value没有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,476
精华内容 25,390
关键字:

为什么stringbuffer是线程安全的