调用synchronized,会优先使用偏向锁和轻量级锁加锁,有竞争后才会升级为重量级锁。
对象obj,对象头中有MarkWord,结构状态如下,它会存储对象的哈希码、分代年龄、锁状态等。
一开始对对象加锁,如果没有禁用偏向锁,那么初始时加的是偏向锁,MarkWord对应上图State为Biased状态。(禁用了偏向锁,初始加的就是轻量级锁)
对象头MarkWord中的biased_lock表示偏向状态,默认是开启偏向状态的。即初始状态对象头MarkWord中biased_lock为1,最后3位为101。这时thread还没值,等到有线程对该对象加锁,thread会变成该线程的id(这个id不是java中Thread.currentThread().getId()的id,而是操作系统给线程分配的唯一标识),表示这个锁属于这个线程了。直到有别的线程来对这个对象加锁(即这个对象不止会被一个线程用),偏向锁就会升级为轻量级锁,MarkWord最后两位为00,前面62位记录锁记录地址。
- 加锁前,MarkWord最后3位为101,偏向状态开启,但线程id还没值,因为还没有线程对其加锁。
- 加锁后,线程id就有值了。
- 解锁后,线程id值仍然保留,标记着这个对象锁属于这个线程。
轻量级锁和偏向锁都是无竞争的,也就是线程来访问对象加锁的时间是错开的,只是偏向锁说明这个对象锁只属于一个线程,而如果出现多个线程对这个对象加锁,就会变成轻量级锁。如果有竞争,会升级为重量级锁。在其它线程使用了锁导致偏向锁升级称为轻量级锁的情况下,还会将对象变为不可偏向状态,即解锁后,MarkWord最后三位是001。
轻量级锁加锁流程:
Thread-0来对其进行加锁,Thread-0会在自己的栈帧中创建一个锁记录(Lock Record),它有自己的锁记录地址和对象引用,它尝试对对象obj加锁,就让自己的对象引用指向obj对象,并尝试用cas将锁记录地址与obj对象中MarkWord内容交换,如果交换成功,锁记录中存储MarkWord原来内容(hashCode、age分代年龄、biased_lock偏向锁状态等),对象的MarkWord中会存储锁记录地址与锁状态。(解锁后,又会用cas再交换回来)
图示加锁前:
图示加锁成功后:
再有Thread-1来对obj进行加锁,它cas交换就会失败,因为obj已经被Thread-0加锁了,那么有竞争了,会进行锁膨胀,Thread-1会去为obj对象申请一个Monitor,让obj指向Monitor,然后Thread-1进入Monitor的EntryList队列中阻塞等待。等到Thread-0执行完同步代码块内容,准备释放锁,它根据对象引用找到obj对象,cas想把数据交换回来,发现失败,因为锁已经膨胀了,于是它根据obj对象中记录的Monitor地址,找到Monitor,把owner置为null,表示解锁,并且唤醒EntryList阻塞队列中等待的线程。
关于Monitor:
每个Java对象都可以关联一个Monitor,当对象被加重量级锁后,对象头中MarkWord中就hi被设置为指向该Monitor的指针。
对对象加重量级锁时,线程会去跟操作系统申请Monitor(锁),Monitor中有Owner、EntryList、WaitSet,Owner是拥有该锁的线程,EntryList是获取锁失败的线程阻塞等待的队列,WaitSet是线程调用wait()方法后在此等待唤醒的休息室。
在尝试加轻量级锁,cas交换数据时,还有一种失败的情况,就是锁重入,如果同一个线程多次对obj加锁,那么第二次,它会cas失败,然后会在栈帧中再创建一个锁记录,有多少个锁记录,就表示锁重入了多少次。那么解锁时,也要解这么多次。
批量重偏向
偏向锁被其它线程访问导致升级为轻量级锁,也叫撤销偏向,当撤销达到一定阈值,仍有机会进行重偏向。
比如有30个对象都被线程t1加了偏向锁,然后又让线程t2去对这30个对象加锁,t2对对象加锁时,就会让偏向t1的锁升级为轻量级锁,撤销偏向t1,撤销次数达到一定阈值(20),就会让后面的对象(10个)批量重偏向t2(jvm觉得这些对象不应该给t1,都被t2访问这么多次,t1被撤销那么多次),这时重偏向t2的对象的MarkWord中最后3位为101,前面54bit存放t2的线程id,它们解锁后MarkWord也是最后3位为101,前面54bit记录着t2的线程id。
批量撤销
当撤销偏向次数达到更高的阈值(40),jvm会认为自己确实偏向错了,就根本不该给它偏向,于是整个类的对象都会变为不可偏向状态,新创建的对象也是不可偏向的。
比如,现在有40个对象,线程t1对它们加锁,40个对象都是偏向t1的。然后,让线程t2来对这40个对象加锁,那么前19个对象,会被撤销偏向t1,升级为轻量级锁,不可偏向状态,第20个对象,达到撤销阈值了,先撤销它对t1的偏向,然后偏向t2,后面20个对象也跟着重偏向t2,这时撤销次数是20。再让线程t3来对这40个对象加锁,前面19个都是不可偏向状态,加了锁直接就是轻量级锁,从第20个到39个,因为是偏向t2,被t3访问加锁,所以又开始撤销,撤销到第39个,撤销次数20,加上前面的20,达到撤销阈值40,于是该对象整个类都变成不可偏向状态。