引言
synchronized 是Java种的一种保证线程安全的加锁方式,要搞懂他的原理,我们就得明白以下概念。
- 上下文切换:我们都知道操作系统在用户态和内核态直接切换是十分浪费资源的,所以我们得避免这种上下文的频繁切换,对此
synchronized引入了锁升级,膨胀机制,从偏向锁,到轻量级锁,到重量级锁的过程。- Java中的对象长什么样子?
- 对象头长什么样子?
- 是怎么加锁的,冲突了怎么办,怎么解锁?
Java中对象和Monitor
对象
对象中有对象头,实例数据
对象头(mark word)长啥样
hashcode,分代年龄,是否是偏向锁,加锁标识
Monitor
monitor 又称管程,也叫锁。是JVM层提供的,每一个对象对应一个monitor。
工作原理
- 用了
synchronized(obj)之后,对象的markword中就有个指针指向monitor的地址,并且monitor中的owner从null变为thread-1. thread-2来抢占共享资源,发现monitor已经被抢占了,就去阻塞队列里面排队,变成阻塞状态.synchronized(obj)执行完之后会唤醒阻塞队列中的线程,此时是非公平的
轻量级锁
- 加锁 每个线程在加锁的时候会产生一个锁记录,锁记录中的
lock-recoad-address 00与 对象头中的markwordcas交换,轻量级锁加锁成功 - 冲突 同一个线程重新获取锁,会产生一个新的锁记录,因为已经加锁了,就cas不成功,锁记录的地址为null
- 解锁 锁记录为null的话就表示是重入的锁,不为null就交换markword.
锁膨胀
- 加锁 在刚才的冲突环节,如果不是自己的线程,那么会锁就会膨胀,对象的markword就指向
Monitor地址 10此时thread-1就进入entryList阻塞队列中,等待唤醒. - 解锁 等thread-0 执行完之后,发现cas交换失败了,就会进行重量级锁的解锁,将
Monitor中的owner赋值null,并且唤醒EntryList中的线程.
偏向锁
- 加锁 我们发现轻量级锁每次同一个线程还是需要cas检查,这也是很耗费性能的,JVM对这个进行了优化,默认是开启偏向锁.不采用锁记录和对象头交换的形式,而是直接将线程的地址和对象的markword交换,这样每次只需要看当前来的线程是本线程就不需要cas了.
结论
synchronized 默认是加偏向锁,这样就避免了cas自旋去交换lockrecoad地址和对象头地址指针。当多个线程交替,没有冲突的时候,(JVM会认为此时把共享资源只分配给一个人不合适)偏向锁升级为轻量级锁,再次强调轻量级锁是没有冲突的情况下。如果此时发生了冲突,两个线程同时都要抢占共享资源,那么轻量级锁就会膨胀成重量级锁这个时候会发送一次上下文切换。对象头的锁指针指向Monitor的地址,并且Monitor的Owner为当前获取共享资源线程的地址,加锁标志位置为10.没有抢占到共享资源的就进入阻塞队列等待被唤醒。
ps:文章是查阅相关资料以及个人理解总结而成,如果有错误的地方,欢迎批评指正。