看了synchronzied的底层实现的原理,在JVM中,锁有专门的名字,对象监视器。当多个线程来请求同一个对象监视器时,这时候synchronized就起作用了。
线程请求的流程
首先请求获得锁的线程会先进入到Contention列表中,再从列表选中一些线程进入到EntryList中,再从EntryList中选择一个线程成为OnDeck(同一时间只有一个Ondeck),然后再执行(Owner状态),执行完释放锁(!Owner状态),然后再从Entry中选择一个线程进入OnDeck状态
Contention
FREE-LOCK结构,其并不是一个真正的队列,而是链表结构,新进来的为头节点(CAS操作),进入EntryList的为未节点,每次出列都是由Owner进行调度的
EntryList
作用是:ContentionList会被线程并发访问,为了降低对 ContentionList队尾的争用,而建立EntryList。从ContentionList中进来的方式跟进Contention一样,一般Owner运行完(既unlock)之后会从EntryList中选择一个线程作为OnDeck(一般是头节点),这叫‘竞争切换’。Owner进入wait()进入WaitSet队列,notify之后会再次进入到EntryList中。
自旋锁
线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响 锁的性能,所以自旋就是每次争用失败后先等一等,实在争不到再进入阻塞状态,而java synchronized实现自旋的方式便是:在进入ContentionList之前先去尝试获得锁,并自旋,没有成功才进入队列(Contention、Entry、WaitSet里面的线程都是阻塞状态的)。所以这样对进入到队列的线程是不公平的,而且获得锁还会导致OnDeck状态的线程无法进入Owner,也是不公平的
偏向锁
用于无竞争的情况下,主要用于单线程时,不存在锁竞争的情况,由于加锁放锁(即使是轻量级锁的枷锁放锁都是CAS)会造成额外的系统消耗,所以偏向锁会有一个指针ID,标记偏向的线程,一般来说就是第一次来获得锁的线程,下次还是这个ID的线程则直接进入,若不是这个ID的就更改这个指针ID标志,如有两个线程竞争了,则膨胀为轻量级锁
轻量级锁
也是在无实际竞争的情况下,即多个线程都来前后的获得锁,即某个线程获得了锁,用完了就放,等放了锁才有其他线程来申请锁,不会存在竞争的情况,如果有竞争,则升级为重量级锁
入锁
首先查看两个标志位(锁标志位为01,偏向锁标志位00)才能进行获取,否则可能锁标志位为00(代表有线程获取了该锁,也可能是自己获取了)。首先在线程的堆栈中开辟一块Lock record区域,拷贝Mark Word到线程的Lock record区域中,基于CAS将所对象的指针指向Lock record和将线程的owner指针指向锁对象,此操作如果成果,代表获得锁成功,锁标志位置为00,不成功则查看锁对象的指针是否指向自己,是的话重入执行,不是的话代表有竞争,所标志置为10,然后膨胀为重量级锁。再后面的线程则阻塞,而本线程自旋,尝试获取锁。
放锁
将自己的Lock record的内容基于CAS替换锁对象的Mask word,成功则完成同步,否则则是已经升级为重量级锁,释放锁的同时,唤醒挂起的线程。
总结
synchronized原理便是,JVM中有对象监视器,线程需要取得对象监视器才能进行操作,调用的机制便是自旋后阻塞,竞争切换后继续竞争锁。不公平,但是保证了吞吐量
JVM底层实现
以下列代码为例
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
上面代码编译成class指令后
可以看出synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同 步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图 获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取 锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设 为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当 前线程就要阻塞等待,直到锁被另外一个线程释放为止。
换成同步方法
public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来 辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
参考:https://blog.csdn.net/tingfeng96/article/details/52219649