一、前言
根据之前的锁策略,可以总结出, synchronized 具有以下特性( JDK 1.8):
1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁。
2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁。
3. 实现轻量级锁的时候可能用到的是自旋锁策略
4. 是一种不公平锁
5. 是一种可重入锁
6. 不是读写锁
对于synchronized来说,多个线程,针对同一个对象加锁,就会产生阻塞等待,synchronized 内部其实还有一些优化机制,存在的目的就是为了让这个锁更高效,更好用。
二、锁升级
在加锁之前是无锁状态,在进行加锁的时候,首先会进入到偏向锁状态(偏向锁,并不是真正的加锁,而是相当于占了一个位置,有需要的才去真正的加锁,没有需要就不加锁了)。偏向锁的这个过程,有点相当于“懒汉模式”的懒加载差不多,“非必要,不加锁”。
进入sychronized的时候,并不是真的加锁,先处在偏向锁状态,做个标记(这个过程是非常轻量的)。
如果整个使用锁的过程中,都没有出现锁竞争,在synchronized执行完之后,取消偏向锁,回到无锁状态。但是,如果使用过程中,另一个线程也尝试加锁,那么在它加锁之前,迅速的把偏向锁升级成真正的加锁状态!!!另一个线程也就只能阻塞等待了。
当synchronized发生锁竞争的时候,就会从偏向锁,升级成轻量级锁。此时, synchronized相当于是通过自旋的方式,来进行加锁的。如果要是很快别人就释放锁了,自旋是划算的,但是如果迟迟拿不到锁,一直自旋,并不划算,synchronized自旋不是无休止的自旋,自旋到一定程度之后,就会再次升级成重量级锁(挂起等待锁)。此时,如果线程进行了重量级锁的加锁,并且发生锁竞争,此时线程就会被放到阻塞队列中,暂时不参与CPU调度了,然后直到锁被释放了,这个线程才有机会被调度到,并且有机会获取到锁。
另外,锁升级后不能降级了。
三、锁消除
编译器智能的判定,看当前的代码是否是真的要加锁,如果这个场景不需要加锁,程序猿也加了,那么就会自动把锁给干掉。比如:StringBuffer 带有synchronized,但是如果在单线程中使用StringBuffer, synchronized加了也白加,此时编译器就会直接把这些加锁操作干掉了。
四、锁粗化
锁的粒度: synchronized包含的代码越多,粒度就越粗;包含的代码越少,粒度就越细。
通常情况下,认为锁的粒度细一点比较好。加锁的部分的代码,是不能并发执行的,锁的粒度越细,能并发的代码就越多;反之就越少。
但是有些情况下,锁的粒度粗一些反而更好,两次加锁解锁之间,间隙非常小,此时,不如就直接一次大锁搞定得了。每次加锁可是都有开销的,并发节省的时间,反而不如加锁的开销大。