被优化的synchronized和Lock对比

首先介绍发展史, synchronizedLock对比表格在最后面。(其实写了之后我发现我需要复习JUC了)


1.早期的synchronized
JDK1.6之前属于重量级锁,依赖于操作系统的Mutex Lock,Java的线程映射到操作系统的原生线程,需要操作系统申请互斥量,操作系统对线程的切换,需要从用户态切换到内核态,比较耗时,效率低

2.对synchronized的优化
JDK1.6之后在JVM层面对synchronized底层做了很多的优化,包括偏向锁,轻量级锁,自旋锁,自适应自旋锁,锁消除,锁粗化等优化技术。

2.1 偏向锁
目的:在没有线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量的开销,提升性能。特点:

  • 在没有锁竞争的情况下,会把整个锁消除
  • 偏向于第一个获取到偏向锁的线程
  • 如果在接下来的执行中偏向锁没有被其他线程获取,那么拥有该锁的线程就不需要同步
    变化:在锁竞争激烈的场合,偏向锁失效。原因是,在此情况下,极有可能每次申请锁的线程不是同一个线程,所以此时不应该使用偏向锁,否则得不偿失。

2.2 轻量级锁
当偏向锁失效,JVM不会立即升级为重量级锁,而是试图使用轻量级锁的优化手段(JDK1.6之后加入的)。轻量级锁不是为了替代重量级锁,它的本意是是在没有线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量的开销,提升性能。

目的:和偏向锁一样

特点:

  • 和偏向锁不同,轻量级锁使用CAS操作代替重量级锁。
  • 使用轻量级锁,不需要申请互斥量。
  • 轻量级锁的加锁和释放锁都是CAS操作。
    变化:对于大多数锁来说,在整个同步周期都不存在竞争,这来自经验数据。如果没有竞争,轻量级锁使用CAS操作,避免了使用互斥锁的开销。如果存在竞争,除了互斥锁的开销,还会有额外的CAS操作,所以如果存在锁竞争,轻量级锁比重量级锁更慢。如果竞争激烈轻量级锁会迅速膨胀为重量级锁。关于轻量级锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。

2.3 自旋锁和自适应自旋锁
轻量级锁失效后,JVM避免线程真的在操作系统层面挂起,还会进行一项成为自旋锁的优化手段。在JDK1.6之前就有这项技术了,只是他是默认关闭的,可以通过参数--XX:+UseSpinning开启。JDK1.6之后默认开启。自旋不能完全替代阻塞,因为它还要占用处理器的时间。如果锁被占用的时间短,那么自旋锁的效果就好;否则,反之。自旋等待的时间必须固定,如果超过限定的次数,仍然没有获取到锁,就挂起线程。自旋默认10次,可以使用参数--XX:PreBlockSpin修改。

2.3.1 为什么会有自旋锁
互斥同步对性能最大的影响是阻塞的实现,因为线程的挂起和恢复都需要转入内核态去完成(用户态到内核态的转换将会耗费一定的时间)。而一般线程持有锁的时间并不会太长,如果仅仅为了这一点时间而挂起或恢复线程将会得不偿失。所以JVM团队就想:"能否让后面来的请求获取锁的线程等待一会儿而不被挂起?看看持有锁的线程是否很快就会释放锁"。目的:为了减少线程的挂起和恢复,减少带来的系统开销,引入自旋锁。

2.3.2 自旋的特点
执行忙循环
自旋次数固定(默认10次)
JDK1.6之前默认关闭,之后默认打开
效果的好坏依赖于锁被占用的时间的长短

3.Synchronized和ReEntrantLock对比
① 两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

区别:

synchronized Lock
JVM层面,底层:monitorenter/monitorexit API层面
不需要手动释放锁 需要手动释放
不能中断 trylock设超时时间,或lockInterruptibly放入代码块设超时时间
只能是非公平锁 选择true就变为公平锁
不能精准唤醒 Lock可以绑定多个Condition,signal某个flag实现精准唤醒(我觉得这个不同点最重要)

性能已不是选择标准!!

在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的, JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了。而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步。优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。

所以说,实际上是ReentrantReadWriteLock才因为可以“读读共存”都用myReadWriteLock.readLock().lock();加锁并不互斥地读而高于synchronized的性能,而单一的ReentrantLock并没有比synchronized性能高

你可能感兴趣的:(被优化的synchronized和Lock对比)