1. Java锁的重偏向机制
1.1 偏向锁机制
我们知道,当我们使用synchronized关键字的时候,一个对象a只被一个对象访问的时候,对对象加的锁偏向锁,如果之后出现第二个线程访问a的时候(这里只考虑线程交替执行的情况,不存在竞争),不管线程1是已死亡还是运行状态,此时锁都会升级为轻量锁,并且锁升级过程不可逆。
1.2 批量重偏向
但是如果有很多对象,这些对象同属于一个类(假设是类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());
}
//t1线程对前20个对象加锁
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();
//t2线程再次对前20个对象加锁
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个对象(i=19)开始是偏向锁。
下面打印出来的对象头第一行第一个字节的后三位如下:
倒数第三位(偏向标识) | 最后两位(lock标识) | 锁 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量锁 |
t2线程i=1的对象头第一行第一个字节是: 01000000 最后三位000,是轻量锁。
t2线程i=19的对象头第一行第一个字节是:00000101 最后三位101,是偏向锁。
4、总结
JVM对synchronized锁做了很多优化,这也是为什么synchronized锁的性能和ReentrantLock相比较的原因,在这些优化之后,synchronized和ReentrantLock性能上不相上下了,使用上更加方便,是最好的选择
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。