锁的分类
主要分类包括乐观锁及悲观锁;从另一个角度来说也可以分为公平锁及非公平锁,synchronized机制的锁是非公平锁,这一点是从竞争机制来说,对某个锁的获得不是先到先得,有可能后来者居上(自璇锁)。
锁的实现机制
JAVA中锁机制的实现主要有两种,一种是基于JVM层面的synchronized 另一种是基于JAVA语言层面的Lock。
synchronized的实现原理剖析
用法
synchronized是可重入不可中断的。synchronized的用法主要有三种。基于静态方法,基于实例方法,基于代码块(需显示指定锁的对象)。
细节原理背景
对象监视器(monitor)
每一个java对象都会有一个与之关联的监视器,该监视器的生命周期可视为与该对象一致,同生共死。可认为,获得该监视器即获得该对象的锁。监视器的基本数据结构有一个waitSet(wait()),一个entrySet(竞争该对象,处于阻塞状态),Owner(线程ID,标识谁拥有monitor)。notify ,wait,notifyAll的底层实现也是基于monitor的
Java对象头
Java 对象在堆中存储实例可以分为三个部分:对象头,实例数据,填充数据。其中对象会存储一些与锁相关的信息,根据对象的不同阶段,对象头信息会动态变化。具体信息可参照下图
锁的实现
对于同步代码块
对于代码块是通过两条JVM指令来实现的monitorenter 及 monitorexit
对于同步方法
对于同步方法,是用过隐式标识来实现,方法区的常量池中,有标识能够认定改方法是静态方法,然后加以控制。
其实无论是同步代码块抑或是同步方法,本质都是通过对象来关联对象监视器进行实现的。执行线程将都先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
锁的优化
JDK 1.6以前synchronized是基于操作系统的Mutex Lock来实现的。这种实现涉及到内核态及用户态的频繁切换,性能较低,故称之为重量级锁。在1.6之后,进行了诸多的优化。注意一点的是,锁的等级是只可升,而不可降级。这样做是为了提高锁的获得和释放的效率
偏向锁
发现,很多情况下,某个同步方法只会由同一个线程使用,这时候要是加锁,对系统的性能是不利的,于是引入了偏向锁。引入了偏向锁,对象的对象头的状态会发现改变,并且会记录该线程的线程Id.。下次线程再使用的时候,只需简单对比一下该线程Id是否为自己即可。偏向锁的释放,需要有竞争,否则该锁是长期拥有的。偏向锁,适用于某一同步方法长期时间下,仅会被同一线程所调用。
轻量级锁。
自旋锁
线程的阻塞与唤醒需要cpu从用户态切换到内核态,频繁的切换对cpu来说是一个很大负担,而且我们发现有些线程的阻塞只是很短的时间,于是引入自璇锁,让线程执行一些空循序。
重量级锁
重量锁就是基于的(Monitor),它很像C中的Mutex,除了具备Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。
锁消除
比如在局部变量中StringBuffer 的锁会被消除,与StringBuilder一致。
对于Lock锁机制实现原理的剖析
Lock是基于Java语言实现的,是乐观锁,自带公平和非公平两种模式,并且是可重入可中断的。
Lock的基本API
主要实现原理
CHL(双向链表,FIFO,存储线程信息)
所有无法获得锁的线程,都需入队列.。唤醒时首先唤醒队列头的线程
CAS(进入队列,及出队列,更新状态信息的原子操作基本原理)
LockSupport.park()
调用操作系统相关(mutuxe)阻塞线程,可指定阻塞时长,唤醒后需重新参与锁竞争。
LockSupport.unpark(Thread thread)
调用操作系统相关(mutuxe)唤醒阻塞的线程
Synchronized与Lock的对比
当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。
参考资料:
各种锁的详细介绍 :https://www.cnblogs.com/wade-luffy/p/5969418.html
锁的膨胀与升级:https://blog.csdn.net/choukekai/article/details/63688332