目录
synchronized
简介
同步块会形成monitorenter,monitorexit字节码指令,这两个指令都需要一个reference类型的参数来指明锁定和解锁的对象.如果代码明确指定了对象参数,则以这个对象的引用作为reference,如果没有明确指定,将根据synchronized修饰的方法类型(如实例方法或类方法),来决定是取代码所在的对象实例还是取对应类型的Class对象来作为线程要持有的锁.
《jvm虚拟机在规范》要求,执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经持有了那个对象的锁,就把该锁的计数器的值+1,在执行monitorexit的时候将值-1.一旦减少为0,则释放锁.如果获取对象锁失败,则阻塞等待,知道锁定的对象被持有它的线程释放.
- 被synchronized修饰的同步块对同一条线程来说时可重入的.即使反复重入,也不会把自己锁死.
- 被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件的阻塞后面的线程进入.无法强制释放锁,也无法强制正在等待的锁超时退出或者中断.
java的线程是映射到系统内核线程之上的,如果要阻塞或者唤醒一条线程,则需要操作系统来帮忙,不可避免地陷入用户态到核心态的转换中,进行这种状态转换需要耗费很多处理时间.
锁升级过程-synchronzied的优化
偏向锁—->轻量级锁—->重量级锁
偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
一个对象刚开始实例化的时候,没有任何线程来访问它的时候。
它是可偏向的,意味着,它现在认为只可能有一个线程来访问它
所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。
偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。