1、偏向锁引入
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行cas操作。
Java 6引入偏向锁进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只有不发生竞争,这个对象就归改线程所有。
2、偏向状态
64位Java对象头Mark Word结构:
Mark Word(64 bits) | State | |||||
unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal |
thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
ptr_to_lock_record:62 | 00 | LightWight Locked | ||||
ptr_to_heavyweight_monitor:62 | 10 | HeavyWeight Locked | ||||
11 | Marked for GC |
一个对象创建时:
-
如果开启了偏向锁(默认开启),那么对象创建成功后,markword值为0x05即最后3位为101,这是它的thread、epoch、age都为0
-
偏向锁模式是延迟的,不会在程序启动时立即生效。如果想立即生效,可以加VM参数:-XX:BiasedLockingStartupDelay=0来禁用延迟
// 已添加禁用延迟-XX:BiasedLockingStartupDelay=0 public class Person { } @Slf4j(topic = "security.lock.TestBiased01") public class TestBiased01 { public static void main(String[] args) throws InterruptedException { Person p = new Person(); log.debug(ClassLayout.parseInstance(p).toPrintable()); synchronized (p) { log.debug(ClassLayout.parseInstance(p).toPrintable()); } log.debug(ClassLayout.parseInstance(p).toPrintable()); } } // 测试结果 OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0) 0 8 (object header: mark) 0x0000000002198805 (biased: 0x0000000000008662; epoch: 0; age: 0) 0 8 (object header: mark) 0x0000000002198805 (biased: 0x0000000000008662; epoch: 0; age: 0)
-
如果没有开启偏向锁,那么对象创建后,markword值为0x01即后3位为001,这是它的hashcode、age都为0,第一用到hashcode即调用object.hashcode()时才会赋值。
- 添加VM参数:-XX:-UserBiasedLocking ,禁用偏向锁。
3、偏向锁撤销
3.1、hashcode
调用了对象的hashcode,但偏向锁的对象中存储的是线程id,如果调用hashcode会导致偏向锁被撤销
- 轻量级锁会在锁记录中记录hashCode
- 重量级锁会在Monitor中记录hashCode
在调用hashCode后使用偏向锁,需要设置VM参数:-XX:UseBiasedLocking
//在上面的测试中,创建对象之后,调用
System.out.println("hashcode: " + p.hashCode());
// 测试结果
hashcode: 1626877848
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000060f82f9801 (hash: 0x60f82f98; age: 0)
0 8 (object header: mark) 0x000000000252f848 (thin lock: 0x000000000252f848)
0 8 (object header: mark) 0x00000060f82f9801 (hash: 0x60f82f98; age: 0)
3.2、多线程(不发生竞争)
当其他线程使用偏向锁对象时(不发生竞争),会将偏向锁升级为轻量级锁。
@Slf4j(topic = "s.RevokeBiasedLock")
public class RevokeBiasedLock {
public static void main(String[] args) {
Person p = new Person();
new Thread(() -> {
log.debug(ClassLayout.parseInstance(p).toPrintable());
synchronized (p) {
log.debug(ClassLayout.parseInstance(p).toPrintable());
}
log.debug(ClassLayout.parseInstance(p).toPrintable());
synchronized (RevokeBiasedLock.class) {
RevokeBiasedLock.class.notify();
}
}, "t1").start();
new Thread(() -> {
synchronized (RevokeBiasedLock.class) {
try {
RevokeBiasedLock.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(p).toPrintable());
synchronized (p) {
log.debug(ClassLayout.parseInstance(p).toPrintable());
}
log.debug(ClassLayout.parseInstance(p).toPrintable());
}, "t2").start();
}
}
// 测试结果
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
0 8 (object header: mark) 0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001ef6f190 (thin lock: 0x000000001ef6f190)
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
3.3、wait-notify
一旦调用wait-notify,都会升级为重量级锁,后面wait-notify在详细介绍。
4、批量重定向
如果对象虽然被多个线程访问,单没有竞争,只是偏向了线程t1的对象仍然有可能重新偏向t2,重偏向对重置对象的Thread ID
当撤销偏向锁阈值超过20次后,jvm会在给这些对象加锁时,重新偏向至加锁线程。
@Slf4j(topic = "s.BatchReBias")
public class BatchReBias {
public static void main(String[] args) {
Vector<Person> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Person p = new Person();
list.add(p);
synchronized (p) {
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 30; i++) {
Person p = list.get(i);
log.debug("==================");
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
synchronized (p) {
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
}, "t2");
t2.start();
}
}
// 测试结果
t1 1
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001de4e805 (biased: 0x000000000007793a; epoch: 0; age: 0)
...
t2 1
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
0 8 (object header: mark) 0x000000001ec8f0c0 (thin lock: 0x000000001ec8f0c0)
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
...
t2 20
0 8 (object header: mark) 0x000000001de4e805 (biased: 0x000000000007793a; epoch: 0; age: 0)0 8 (object header: mark) 0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
...
5、批量撤销
当撤销偏向锁阈值超过40次后,整个类的所有对象都会变为不可偏向,新建的对象也是不可偏向的。
@Slf4j(topic = "s.BatchRevoke")
public class BatchRevoke {
static Thread t1, t2, t3;
public static void main(String[] args) throws InterruptedException {
Vector<Person> list = new Vector<>();
int num = 39;
t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
Person p = new Person();
list.add(p);
synchronized (p) {
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
for (int i = 0; i < num; i++) {
Person p = list.get(i);
log.debug("==================");
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
synchronized (p) {
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("==================>");
for (int i = 0; i < num; i++) {
Person p = list.get(i);
log.debug("==================");
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
synchronized (p) {
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Person()).toPrintable());
}
}
// 测试结果
t1 1
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
...
t2 1
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001e07f2a8 (thin lock: 0x000000001e07f2a8)
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
...
t2 20
0 8 (object header: mark) 0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001e2ea905 (biased: 0x0000000000078baa; epoch: 0; age: 0)
0 8 (object header: mark) 0x000000001e2ea905 (biased: 0x0000000000078baa; epoch: 0; age: 0)
...
t3 1
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
0 8 (object header: mark) 0x000000001ee6eeb8 (thin lock: 0x000000001ee6eeb8)
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
主线程 新创建对象
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
6、锁消除
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
// JIT 即时编译器
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
// 测试结果
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 2.256 0.046 ns/op
c.i.MyBenchmark.b avgt 5 2.239 0.036 ns/op
- BenchmarkMode(Mode.AvrageTime):模式-计算平均时间
- @Warmup(iterations = 3):热身-3次
- @Measurement(iterations = 5):测试-5次
- @OutputTimeUnit(TimeUnit.NANOSECONDS):计量单位-纳秒
JIT即时编辑器,对热点代码的java字节码优化。
Object o = new Object(); 对象o为局部变量,加锁没有效果,此时,JIT会对synchronzied优化,实际执行并不会有synchronized加锁操作,所有性能接近。
-
VM 参数 -XX:-EliminateLocks 控制开关,关闭不会执行JIT优化,在此查看结果:
Benchmark Mode Samples Score Score error Units c.i.MyBenchmark.a avgt 5 2.209 0.034 ns/op c.i.MyBenchmark.b avgt 5 21.753 0.580 ns/op
性能差距明显。
仓库地址:https://gitee.com/gaogzhen/concurrent