1. synchronized的实现原理与应用
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
synchonized在JVM里的实现原理:
代码块同步是使用monitorenter 和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
2. 对象头 MarkWord
在synchronized锁中,对象头是一个很重要的部位,在整个锁升级的过程中都会用到对象头
一个空的对象(new Object())的MarkWord是8字节,其中包括
类型指针 在开启压缩的情况下是4字节 ,不开启是8字节
实例数据 根据类型来判断大小,如果是引用类型是地址,空对象中没有所以是0
对齐填充 不够8的倍数,进行补充
3. 锁升级过程
锁升级一共有4种状态
无锁 --> 偏向锁 --> (自旋锁,自适应自旋) 轻量级锁 --> 重量级锁
1. 偏向锁
在无竞争的情况下使用偏向锁,可以在获取到偏向标志后,线程id 会存储在对象头中,一直给当前线程使用,如果有另一个线程请求获取锁则偏向锁结束。而且计算过hashCode 的对象无法使用偏向锁
2. 轻量级锁
互斥锁对性能最大的影响是阻塞,因为需要切换到内核态来完成,所有给java虚拟机的并发性能带来了很大的压力,大多数共享数据的锁定持续时间都很短,所有有了轻量级锁。
再有少量竞争的情况下,synchronized会尝试使用轻量级锁,synchronized会把当前的锁的信息封装成一个 lock record ,记录在对象头上,对象头上记录的是哪个线程,哪个线程就获取到锁。
另一个线程会通过自旋的方式尝试获取锁,如果线程自旋时间过长,或者竞争过于激励(超过2个以上的线程同时尝试获取锁)就会膨胀成重量级锁
3.重量级锁
当synchronized锁升级为重量级锁时,会对锁的性能有很大的影响,因为整个加锁的过程需要从用户态切换到内核态,由内核态来完成加锁和解锁的过程,底层会调用 lock comxchg 命令来进行加锁。