1. Java锁的重偏向机制
我们知道,当我们使用synchronized关键字的时候,一个对象a只被一个对象访问的时候,对对象加的锁偏向锁,如果之后出现第二个线程访问a的时候(这里只考虑线程交替执行的情况,不存在竞争),不管线程1是已死亡还是运行状态,此时锁都会升级为轻量锁,并且锁升级过程不可逆。
但是如果有很多对象,这些对象同属于一个类(假设是类A)被线程1访问并加偏向锁,之后线上2来访问这些对象(不考虑竞争情况),在通过CAS操作把这些锁升级为轻量锁,会是一个很耗时的操作。
JVM对此作了优化:
当对象数量超过某个阈值时(默认20, jvm启动时加参数-XX:+PrintFlagsFinal可以打印这个阈值 ),Java会对超过的对象作批量重偏向线程2,此时前20个对象是轻量锁,
后面的对象都是偏向锁,且偏向线程2。
2.代码
public static void main(String[] args) throws Exception {
List<A> list=new ArrayList<>();
//生成40个A的实例
for (int i = 0; i < 40; i++) {
list.add(new A());
}
Thread t1= new Thread(){
public void run() {
for (int i = 0; i <20; i++) {
A a=list.get(i);
synchronized (a) {
if(i==1 || i==19) {
System.out.println("t1 lock " + i );
//打印对象头
out.println(ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
}
}
};
t1.start();
t1.join();//通过join是t1结束后再启动t2,避免竞争
new Thread().start();
Thread t2= new Thread(){
public void run() {
for (int i = 0; i < 20; i++) {
A a=list.get(i);
synchronized (a) {
if(i==1 || i==19) {//i<19的时候轻量锁, i>=19的时候是偏向锁
System.out.println("t2 lock " + i );
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
}
};
t2.start();
t2.join();
}
3.执行结果
t1 lock i=1
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c0 0c 1d (00000101 11000000 00001100 00011101) (487374853)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 lock i=19
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c0 0c 1d (00000101 11000000 00001100 00011101) (487374853)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2 lock i=1
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f5 f7 1e (01000000 11110101 11110111 00011110) (519566656)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2 lock i=19
com.luban.layout.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 d1 1b 1d (00000101 11010001 00011011 00011101) (488362245)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
从上面的t1线程的结果全是偏向锁。 t2线程中,前19个对象是轻量锁,第20个对象开始是偏向锁。 下面打印出来的对象头第一行第一个字节的后三位如下:
| 倒数第三位(偏向标识) | 最后两位(lock标识) | 锁 |
|---|---|---|
| 0 | 01 | 无锁 |
| 1 | 01 | 偏向锁 |
| 0 | 00 | 轻量锁 |
t2线程i==1的对象头第一行第一个字节是: 01000000 最后三位000,是轻量锁。
t2线程i==19的对象头第一行第一个字节是:00000101 最后三位101,是偏向锁。