偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
偏向状态
回忆一下对象头格式
一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
- 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值
测试偏向锁
/**
* @author WGR
* @create 2021/1/28 -- 15:20
*/
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) {
Dog d = new Dog();
log.debug( ClassLayout.parseInstance(d).toPrintable());
sleep(3);
log.debug( ClassLayout.parseInstance(new Dog()).toPrintable());
}
}
class Dog{
}
因为偏向锁有几秒的延迟。-XX:BiasedLockingStartupDelay=0
当取消延迟后发现,2个锁标准都是101,变成了偏向锁。
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) {
Dog d = new Dog();
log.debug( ClassLayout.parseInstance(d).toPrintable());
synchronized (d){
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
}
class Dog{
}
注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
测试禁用
在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
禁用后直接是轻量级锁。
撤销 -HashCode
调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销
轻量级锁会在锁记录中记录 hashCode,重量级锁会在 Monitor 中记录 hashCode.
/**
* @author WGR
* @create 2021/1/28 -- 15:20
*/
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) {
Dog d = new Dog();
d.hashCode();
log.debug( ClassLayout.parseInstance(d).toPrintable());
synchronized (d){
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
}
class Dog{
}
撤销 - 其它线程使用对象
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
/**
* @author WGR
* @create 2021/1/28 -- 15:20
*/
@Slf4j(topic = "c.TestBiased")
public class TestBiased {
public static void main(String[] args) {
Dog d = new Dog();
new Thread(() ->{
log.debug( ClassLayout.parseInstance(d).toPrintable());
synchronized (d){
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
log.debug( ClassLayout.parseInstance(d).toPrintable());
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
},"t1").start();
new Thread(() ->{
synchronized (TestBiased.class){
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug( ClassLayout.parseInstance(d).toPrintable());
synchronized (d){
log.debug( ClassLayout.parseInstance(d).toPrintable());
}
log.debug( ClassLayout.parseInstance(d).toPrintable());
},"t2").start();
}
}
class Dog{
}
结果
16:08:42.918 c.TestBiased [t1] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16:08:42.921 c.TestBiased [t1] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16:08:42.922 c.TestBiased [t1] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16:08:42.923 c.TestBiased [t2] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16:08:42.924 c.TestBiased [t2] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 80 f2 1d 1f (10000000 11110010 00011101 00011111) (522056320)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16:08:42.925 c.TestBiased [t2] - com.dalianpai.thread.Dog object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的
static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + " " + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}