StringBuilder和StringBuffer的区别作为一个在java面试里面老生常谈的问题,一般的区别知道是因为一个做了同步一个没做,导致在单线程和线程安全的情况下StringBuffer会比StringBuilder效率差,但是到底具体底层是什么原因呢?我在这里做一个简单探讨。
首先看代码:
StringBuffer:
StringBuffer在每次append的时候都需要进行一个synchronized的消耗
StringBuilder:
StringBuilder不需要,所以StringBuilder在单线程和线程安全情况下性能会更好
下面我们再测试一下StringBuilder和StringBuffer在进行append花费的时间
appendTimes为50次:
appendTimes为
可以看出来buffer耗费时间是builder的4倍以上
那么问题来了,synchronized究竟做了什么导致stringBuffer和stringBuilder在性能上有如此大的差异呢?
由于本次是讨论单线程情况下,所以一下讨论均基于单线程情况下
首先明确下对象监控器(synchronized)的几种状态:
Contention List:所有请求锁的线程将被首先放置到该竞争队列
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
Owner:获得锁的线程称为Owner
!Owner:释放锁的线程
下图转换状态:
每个线程首先会被放进contentionList里面(采取一个后进先出的机制,类似于栈),当某个owner线程执行unlock的时候,如果entryList为空,则会从contentionList里面去拿取一个线程放进去,并且知道你个entryList里面(一般是第一个)作为一个ready线程放进去onDeck(一般只有一个),然后释放锁让onDeck去竞争
这里原本说onDeck需要竞争,这是为什么呢?
原因是jvm会有一个优化,线程在进入contentionList之前会有一个自旋时间,大概是一个线程上下文切换的时间,如果在这个时间有锁释放出来,正在自选的线程就可以和onDeck的线程进行竞争锁
所以在单线程情况下,线程直接进入自旋,发现有锁之后直接获取,在次数较小的访问里面这个是可以忽略的,但是当append次数增多的时候,每次进入之前从自旋状态到获取锁到达owner状态这个期间的消耗就会非常的影响性能