前言
一、string,StringBuffer,StringBuilder 的区别
String是不可变类,每当我们对String进行操作的时候,总是会创建新的字符串。操作String很耗资源,所以Java提供了两个工具类来操作String - StringBuffer和StringBuilder。
线程安全:StringBuffer:线程安全,StringBuilder:线程不安全。因为 StringBuffer 的所有公开方法都是 synchronized 修饰的,而 StringBuilder 并没有修饰。
缓冲区:StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。
性能:StringBuffer 是线程安全的,它的所有公开方法都是同步的,StringBuilder 是没有对方法加锁同步的,因为那维护同步数据的正确性肯定要消耗资源的,所以毫无疑问,StringBuilder 的性能要远大于 StringBuffer。
StringBuffer 的append 方式,是通过String.getChars 方法拷贝的。该方法的作用是将当前字符串从start到end-1位置上的字符复制到字符数组c中,并从c的offset处开始存放
所以,StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景,如果是确定线程安全单线程场合 StringBuilder 更适合。
String类代表字符串,字符串是常量,常量需要进入到内存中的方法区的常量池(进入常量池规则:如果常量池中没有这个常量,就创建一个,如果有就不再创建了)。常量相同的字符串,地址一样,所以如果相同的字符串地址一样。String类重写了equals方法,比较的是属性值,s1和s2的属性值都是“abc”,所以是true
String s = new String(“abc”);s首先会在常量池创建“abc”字符串常量,当new的时候就会在堆内存中创建一个对象,此时会把常量池中的字符串常量拷贝一份副本到给到堆内存中的对象,堆内存中的这个对象就会把地址值赋给s。常量池中对象的地址值和堆内存中对象的地址值是不一样的,s指向的是堆内存中的对象,不是常量池中的对象。此时堆内存中有一个对象,常量池中有一个对象,所以创建了2个对象。
二、CopyOnWrite
并发下线程安全的(因为修改方法加了 ReentrantLock 锁),
底层修改(remove、add等)原理:修改时,数组拷贝原生方法(System.arraycopy)拷贝一个容器,再往新的容器里修改这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址(注:但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据)
- 适合“读多写少”的操作(场景)。
- 需要在遍历期间防止线程间的冲突。如果是其它类型的集合(ArrayList、HashMap),在遍历的时候,修改,就会报错(fail-fast快速失败机制)。
注:fail-fast机制:put和remove(Object)方法中都对modCount进行了加1操作,forEach遍历的时候,如果modCount和expectedModCount不等,则抛出异常。
2.1 CopyOnWriteArrayList
CopyOnWriteArraySet 是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList 额外提供了 addIfAbsent()
和addAllAbsent()
这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!
CopyOnWrite 的机制虽然是线程安全的,但是在add操作的时候不停的拷贝是一件很费时的操作,所以使用到这个集合的时候尽量不要出现频繁的添加操作,而且在迭代的时候数据也是不及时的,数据量少还好说,数据太多的时候,实时性可能就差距很大了。在多读取,少添加的时候,他的效果还是不错的(数据量大无所谓,只要你不添加,他都是好用的)
注:由于修改拷贝的缘故,容器如果很大的话,和容易触发垃圾回收。