synchronized底层实现总结

看了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

           SnailClimb的java面试突击指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值