java基础串联(一) StringBuilder的线程不安全

一、String VS StringBuilder、StringBuffer的区别

String 声明的是不可变的对象

// string底层是由一个final的字符数组来储存数据。
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

StringBuilder StringBuffer 底层都是由一个可变数组实现

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
}

1.0 验证字符串是不可变的

 public static void main(String[] args) {
        String str1 = "abc";
        System.out.println("str1-2 = " + System.identityHashCode(str1));
        String str2 = "abc";
        System.out.println("str1-2 = " + System.identityHashCode(str2));
        str1 = str1 + "c";
        System.out.println("str1+\"c\"=" + System.identityHashCode(str1));
        
        char[] str3 = new char[10];
        System.out.println("char[] =" +System.identityHashCode(str3));
        str3[0] = 'a';
        str3[1] = 'b';
        str3[2] = 'c';
        str3[3] = 'd';
        System.out.println("char[] =" + System.identityHashCode(str3));
    }
    // 输出结果
    // str1-2的内存地址一致,说明"abc"相同的字符串在常量池中,所以栈指向堆的是同一个地址
    str1-2 		= 1935365522
	str1-2 		= 1935365522
	// str1+"c" 内存地址发生了变化 说明字符串本身是不能改变的,只能从新创建一个新的字符串对象
	str1+"c"	=1483022288
	// char[]的内存地址一致,说明对象未发生改变
	char[] 		=1159785389
	char[] 		=1159785389

1.1 StringBuilder VS StringBuffer的区别

相同点:

StringBuilder StringBuffer 继承同一个父类, abstract class AbstractStringBuilder
StringBuilder StringBuffer 底层都是由一个可变长的动态字符数组来储存数据
StringBuilder StringBuffer 拥有相同的API

不同点

StringBuffer 是线程安全的,可以在高并发的场景下使用。
StringBuilder 是线程不安全,在高并发的场景可能发生,
   数据丢失(数据覆盖)
   数组越界(由于添加元素触发数组扩容,导致数组越界)

// 线程不安全的代码如下:
    @Override 
    // java.lang.AbstractStringBuilder#append(char)
    public AbstractStringBuilder append(char c) {
        ensureCapacityInternal(count + 1);
        value[count++] = c;     // count++ 是线程不安全的
        return this;
    }
    
	// 数组越界
	  /**
     * This method has the same contract as ensureCapacity, but is
     * never synchronized.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

    /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    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);
    }

1.1.1 代码验证以上观点

 public static void main(String[] args) throws InterruptedException {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        stringBuffer.append("a");
                    }
                }
            }).start();
        }

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        stringBuilder.append("a");
                    }
                }
            }).start();
        }

        Thread.sleep(1000);
        String stringBufferToStr = stringBuffer.toString();
        log.info("stringBufferToStr is {}", stringBufferToStr.length());

        String stringBuilderToStr = stringBuilder.toString();
        log.info("stringBuilderToStr is {}", stringBuilderToStr.length());
    }

// 输出日志:
	[main] INFO thread.pstring.StringTest - stringBufferToStr is 200000
	[main] INFO thread.pstring.StringTest - stringBuilderToStr is 199701

1.2 为什么StringBuffer是线程安全的

StringBuffer的全部方法都被关键字synchronized 修饰。

	// StringBuffer 部分代码片段
    @Override
    public synchronized StringBuffer append(int i) {
        toStringCache = null;
        super.append(i);
        return this;
    }

    /**
     * @since 1.5
     */
    @Override
    public synchronized StringBuffer appendCodePoint(int codePoint) {
        toStringCache = null;
        super.appendCodePoint(codePoint);
        return this;
    }

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

1.2.1 验证StringBuffer是安全的

	/**
	* 创建两组线程,分别调用方法print print2(print print2 均加锁,synchronized)
	*  希望:
	* 	 同一个对象t1, 在t1调用print方法时(阻塞),调用print2方法的线程会等待获取锁
	* 结果:
	* 	与希望现象一致
	*/
   public static void main(String[] args) throws InterruptedException {
        StringTest t1 = new StringTest();
        t1.name = "StringTest1";
        StringTest t2 = new StringTest();
        t2.name = "StringTest2";

        for (int i = 0; i < 10; i++) {
            String finalI = String.valueOf(i);
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        t1.print(t1.name);
                    }
                }
            }).start();
        }

        Thread.sleep(111);

        for (int i = 0; i < 10; i++) {
            String finalI = String.valueOf(i);
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        t1.print2(t1.name);
                    }
                }
            }).start();
        }

    }


    public synchronized void print(String str) throws InterruptedException {
        if("StringTest1".equalsIgnoreCase(str)){
            log.info("print is StringTest1  阻塞中...........................");
            Thread.sleep(5000);
            log.info("print is StringTest1  阻塞中...........................end");
        }else {
            log.info("print is {}", str);
        }
    }

    public synchronized void print2(String str) throws InterruptedException {
        if("StringTest1".equalsIgnoreCase(str)){
            log.info("xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end");
        }else {
            log.info("xxxxxxxxxxxxxxxxxxxxprint is {}", str);
        }
    }

// 输出日志(日志有部分删减)
 [Thread-0] - print is StringTest1  阻塞中...........................
 [Thread-0] - print is StringTest1  阻塞中...........................end
 [Thread-0] - print is StringTest1  阻塞中...........................
 [Thread-0] - print is StringTest1  阻塞中...........................end

 [Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
 [Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
 [Thread-19] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
 ... 省略部分日志
 [Thread-18] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
 [Thread-10] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end
 [Thread-10] - xxxxxxxxxxxxxxxxxxxxprint2 is StringTest1 ...........................end

 [Thread-9] - print is StringTest1  阻塞中...........................
 [Thread-9] - print is StringTest1  阻塞中...........................end
 [Thread-9] - print is StringTest1  阻塞中...........................
 [Thread-9] - print is StringTest1  阻塞中...........................end
 [Thread-8] - print is StringTest1  阻塞中...........................

1.2.2 验证StringBuilder是不安全的

1.3 关联点 synchronized 关键字的含义是什么?

每一个类对象都对应一把锁,当某个线程t1调用类对象O中的synchronized方法m1时,必须获得对象O的锁才能够执行M方法,否则线程t1阻塞。一旦线程t1开始执行m1方法,将独占对象O的锁。使得其它需要调用O对象的全部被synchronized修饰的方法阻塞。只有线程t1执行完毕,释放锁后。那些阻塞线程才有机会重新调用m1方法。这就是解决线程同步问题的锁机制。
持有锁的对象O在访问方法m1时,会与该对象的全部被synchronized修饰的方法阻塞。锁可以通过方法间传递(循环调用时避免使用死锁)

1.4 关联点 创建线程有哪几种方式?

1.继承Thread
2.实现Runable 无返回值
3.实现callable 有返回值,需要isDone来循环判断,消耗cpu
4.线程池
5.java8提供的stream.parallel

待完善

由String StringBuilder联想到redis的字符,可变长数组。
线程安全的集合 线程不安全的集合
synchronized的作用是什么?
常用的加锁方式有哪些?
ThreadLocalRandom使用了哪些技术点 cas lock等?
线程的声明周期

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值