我们都知道字符串操作类面试极大概率问到的就是StringBuffer与StringBulider的区别,有关于线程安全,效率高,安全性如何?同步与否?等等问题都可能会出现,很多刚找工作的兄弟们是不是背着八股文自己都不知道为什么是这样?
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
那么问题来了:
1、什么是线程安全?什么叫同步?
2、为什么StringBuffer的方法是线程同步的而StringBulider不是?
3、为什么二者之间StringBulider的速率比较高?
1、什么是线程安全?什么叫同步?
这个问题很简单,学过操作系统的兄弟们都知道多进程,而线程是进程的一个子单元,共享一个进程的资源,它们同步或者异步执行完成整个进程所需要完成的工作。现在的计算机大部分都是多道批处理的操作系统,多核的CPU足够的内存完全支持多进程任务的并发。那么在同一个进程下的多个线程的资源共享机制如果没有一定的限制就会出现不安全的现象。举个例子:类似于数据库幻读。当一个资源需要应用程序单独进行改变时,却发现其他的进程或者线程和你一起访问这个资源,那么修改就会出现问题,我还没使用你就给我改掉了?线程安全的概念就是保证多线程正确且正常的执行。
这时候为了应对同时操作现象的发生,同步至关重要,再举个例子:数据库表记录加锁。什么是锁?分为乐观锁与悲观锁甚至因为大小作用又有其他的锁自旋锁,轻量级锁等等。但是无非就是保证数据的正确性,当一个线程在访问一个资源时,其他的线程只能读取不能修改,删除直到这个线程执行结束。可读不可写。同步关键字synchronized就是这个作用。
2、为什么StringBuffer的方法是线程同步的而StringBulider不是?
上面知道同步关键字synchronized就是声明某一个方法是线程安全的。那么我们就会猜测是不是因为StringBuffer的方法里有被synchronized修饰的呢?而StringBulider却没有?
看看StringBuffer源码:
public final class StringBuffer
extends AbstractStringBuilder
implements Serializable, Comparable<StringBuffer>, CharSequence
{
private transient String toStringCache;
@Serial
static final long serialVersionUID = 3388685877147921107L;
@IntrinsicCandidate
public StringBuffer() {
super(16);
}
@IntrinsicCandidate
public StringBuffer(int capacity) {
super(capacity);
}
@IntrinsicCandidate
public StringBuffer(String str) {
super(str);
}
public StringBuffer(CharSequence seq) {
super(seq);
}
@Override
public synchronized int compareTo(StringBuffer another) {
return super.compareTo(another);
}
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return super.capacity();
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
@Override
public synchronized char charAt(int index) {
return super.charAt(index);
}
@Override
public synchronized int codePointAt(int index) {
return super.codePointAt(index);
}
...........省略.....
}
通过这一段源码我们不难发现一些特征:
继承父类 AbstractStringBuilder
private transient String toStringCache; 缓存有关。
@IntrinsicCandidate
public StringBuffer() {
super(16);
} 默认容量16
@IntrinsicCandidate
public StringBuffer(int capacity) {
super(capacity);
} 可自定义容量
几乎所有的字符串操作方法里都含有synchronized关键字包裹。
再看看StringBulider的源码:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{
@Serial
static final long serialVersionUID = 4383685877147921099L;
@IntrinsicCandidate
public StringBuilder() {
super(16);
}
@IntrinsicCandidate
public StringBuilder(int capacity) {
super(capacity);
}
@IntrinsicCandidate
public StringBuilder(String str) {
super(str);
}
public StringBuilder(CharSequence seq) {
super(seq);
}
@Override
public int compareTo(StringBuilder another) {
return super.compareTo(another);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
@IntrinsicCandidate
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
@Override
public StringBuilder append(char[] str, int offset, int len) {
super.append(str, offset, len);
return this;
}
@Override
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
@Override
@IntrinsicCandidate
public StringBuilder append(char c) {
super.append(c);
return this;
}
@Override
@IntrinsicCandidate
public StringBuilder append(int i) {
super.append(i);
return this;
}
.......省略......
}
我们不难发现继承的父类与StringBuffer一样,默认的初始容量也是一样的,但是!它的方法都没有用synchronized关键字修饰。所以它不能保证线程安全。但是它正是因为不是同步的那么操作它这个对象的线程不用排队,会非常快的执行,在单进程的情况下(单应用程序)这个StringBulider肯定比StringBuffer速率高。
至于父类的代码我们就不看了,看到最上面你会发现String这个final类几乎可以算得上这俩的爷爷辈。有兴趣的小伙伴自行研究哈!