| 资源消耗 | 目的 | 场景 | 实现方式 |
偏向锁 | 一个线程只有一次CAS | 单线程进行同步块时,消除轻量级锁的CAS操作。 | 大多数场景为单线程访问同步块,一旦有竞争,偏向锁失效。 | 判断markword是否指现当前线程 |
轻量级锁 | 每次进出同步块,都要进行CAS设置Mark word,出现锁竞争时,自旋获得锁,不进行用户态与内核态切换。 | 以自旋与CAS操作的方式进行线程同步,减少线程态切换带来的性能问题 | 追求相应时间; 同步块执行很快时,一旦超过自旋阈值,则升级为重量级锁 | 判断markword是否指现当前线程的 lock record |
重量级锁 | 每次进出同步块,都要进行CAS设置Mark word,出现锁竞争时,线程阻塞挂起,进行用户态与内核态切换。 | 线程同步 | 追求吞吐量; 同步块执行时间长 | 判断markword是否指现锁对像。 |
偏向锁
Java偏向锁旨在对于无并发争用的前提下,进行真正意义上的无锁同步。个人理解就是线程在获取锁之前先检查是否偏向,偏向了就直接拿对象头中存储的线程ID与当前线程做比较,如果一致就继续执行,不一致就撤销偏向,走无偏向锁定流程。
关于这里面的撤销偏向,是通知已经获取偏向锁的线程撤销(也就是对象头记录的线程ID),还是争用偏向锁的线程(即当前线程)撤销呢?撤销的具体过程又是什么样的呢?
1、偏向锁升级:一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它。
hash | age | 是否偏向锁:1 | 锁标识位:01 |
2、所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头MarkWord成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
线程ID | epoch | age | 是否偏向锁:1 | 锁标识位:01 |
3、一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程。
设置成无锁状态。????无锁状态是下面的哪种????
hash | Age | 是否偏向锁:1 | 锁标识位:01 |
null | epoch | age | 是否偏向锁:1 | 锁标识位:01 |
重新偏向线程2
线程2ID | age | 是否偏向锁:1 | 锁标识位:01 |
4、如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁(偏向锁就是这个时候升级为轻量级锁的)。
先标记为非偏向模式
Hash | age | 是否偏向锁:0 | 锁标识位:01 |
再进行轻量级锁升级
Lock record 地址 | 锁标识位:00 |
此后对象再也回不到偏向锁模式
5、如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
设置成无锁状态。????无锁状态是下面的哪种????
hash | Age | 是否偏向锁:1 | 锁标识位:01 |
null | epoch | age | 是否偏向锁:1 | 锁标识位:01 |