1. 并发中锁的原理
64位虚拟机对象头 (markword占12字节96bit,Klass Word为4字节32bit。没有开启指针压缩后各占64bit)
1.1 导入jol依赖包
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
1.2 查看相关参数
public class Test3 {
public static void main(String[] args) {
// 查看jvm信息
System.out.println(VM.current().details());
System.out.println("===========");
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
}
class A {
boolean flag = true;
}
结果:
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
===========
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 存字节码的文件地址
12 1 boolean A.flag true
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
对应:[Oop(Ordinary Object Pointer), boolen, byte, char, short,int, float, long ,double ]的大小
1.3 jvm计算后的hash参数
public static void main(String[] args) {
A a = new A();
// 没计算hashcode对象头
System.out.println("before hash:");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
// JVM计算hashcode的对象头
System.out.println("-------jvm------0x" + Integer.toHexString(a.hashCode()));
// 计算完成之后的对象头变化信息
System.out.println("after hash:");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
class A{
}
before hash:
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-------jvm------0x1c6b6478
after hash:
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 78 64 6b (00000001 01111000 01100100 01101011) (1801746433)
4 4 (object header) 1c 00 00 00 (00011100 00000000 00000000 00000000) (28)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1-10行是jvm计算hashcode之前的信息,13-21行是计算后的信息。16-17行一共是8位,记录了markdown的信息,18行记录了对应字节码的地址(实例数据),19行为padding(对其填充)
第26位到第56位保存的hashcode值(25位没有使用)(小端存储:数据的高字节存储在高地址中,数据的低字节存储在低地址中)图中是倒着来的
十六进制:1c6b6478
OFFSET SIZE VALUE
0 4 01 78 64 6b (00000001 01111000 01100100 01101011) (1801746433)
4 4 1c 00 00 00 (00011100 00000000 00000000 00000000) (28)
即 01111000 01100100 01101011 00011100 00000000 00000000 00000000 这7个字节是存小端信息的
剩下1个字节记录了:第1位没有使用、第2-5位记录了分代年龄、第6位记录了偏向锁状态、第7-8位记录了锁的状态标志.
偏向锁状态 1位 | 锁的状态 2位 | |||||
---|---|---|---|---|---|---|
未使用 25位 | 哈希值 31位 | 未使用 1位 | GC年龄 4位 | 0 | 01 | 无锁 |
偏向线程信息 54位 | epoch(记录撤销次数) 2位 | 未使用 1位 | GC年龄 4位 | 1 | 01 | 偏向锁 |
线程持有锁的记录 62位 | 00 | 轻量级锁 | ||||
monitor锁的信息 62位 | 10 | 重量级锁 | ||||
—— | 11 | GC标志 |
1.4 偏向锁
1.4.1 偏向锁内容
- 如果开启了偏向锁(默认是开启的),那么对象刚创建之后,Mark Word 最后三位的值101,并且这是它的 Thread,epoch,age 都是 0 ,在加锁的时候进行设置这些的值.
- 偏向锁默认是延迟的(4s),不会在程序启动的时候立刻生效,如果想避免延迟,可以添加虚拟机参数来禁用延迟:-XX:BiasedLockingStartupDelay=0 来禁用延迟
- 为什么要延迟:启动程序的时候jvm会启动很多线程,其中有带sync的,如果上来就是偏向锁,出现竞争的话会有消除偏向锁升级等情况,会影响性能。
处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
举例
正常情况下 ```java public static void main(String[] args) throws InterruptedException {
A a = new A();
synchronized (a) {
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
```
90 19 8d 0b (10010000 00011001 10001101 00001011)
可以看到是轻量级锁。
延迟情况下:
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
A a = new A();
// sleep和new的过程不能反过来,否则就是轻量级锁了。默认是延迟开启的,如果先new的话,jvm就认为他是不可偏向的
synchronized (a) {
// System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
// 效果一样
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
延迟启动5s下,可以看到是偏向锁
05 98 80 3a (00000101 10011000 10000000 00111010)
1.4.2 注意
如果在偏向锁启动后,给对象加锁之前,对象不是无锁而已经是偏向锁的解释:这种情况谁也不偏向,叫做匿名偏向,处于可偏向状态(没有存偏向线程id),所以结论:我们刚刚new出来的对象,如果偏向锁启动是匿名偏向,没有启动就是普通对象。旨在告诉jvm我现在是可偏向的,可以加偏向锁。
处于偏向锁的对象解锁后,线程 id 仍存储于对象头中 ```java //-XX:BiasedLockingStartupDelay=0 public class Test7 { static A a;
public static void main(String[] args) {
a= new A();
System.out.println("before lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
System.out.println("after lock");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
```java
before lock
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 90 80 92 (00000101 10010000 10000000 10010010) (-1837068283)
4 4 (object header) c7 7f 00 00 (11000111 01111111 00000000 00000000) (32711)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after lock
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 90 80 92 (00000101 10010000 10000000 10010010) (-1837068283)
4 4 (object header) c7 7f 00 00 (11000111 01111111 00000000 00000000) (32711)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
before lock时,第5行是偏向锁,但是没有偏向线程id,属于匿名偏向;locking时第15行偏向锁,存储了偏向线程id;after lock 第25行 锁释放了依然是偏向锁,依然存着偏向线程id
1.4.3 偏向锁与轻量级锁的性能对比
轻量级锁: ```java public class Test6 {
public static void main(String[] args) {
A a = new A();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
a.parse();
}
long end = System.currentTimeMillis();
System.out.println( (end - start) + " ms");
} }
class A { int i = 0;
public synchronized void parse(){
i++;
}
}
![截屏2021-05-17 下午5.14.01.png](https://cdn.nlark.com/yuque/0/2021/png/12943861/1621242847322-bbcd97f5-0a0f-46e1-a08b-ead299ed47d7.png#clientId=ud925e247-86d8-4&from=drop&id=u98a75807&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2021-05-17%20%E4%B8%8B%E5%8D%885.14.01.png&originHeight=42&originWidth=190&originalType=binary&ratio=1&size=8145&status=done&style=none&taskId=udb354871-08a0-40f7-8ae7-15f37376be3)
- 改成偏向锁,使用虚拟机参数-XX:BiasedLockingStartupDelay=0
![截屏2021-05-17 下午5.13.38.png](https://cdn.nlark.com/yuque/0/2021/png/12943861/1621242822125-d02fc42c-4c8c-4d14-971c-4c78ee05ce98.png#clientId=ud925e247-86d8-4&from=drop&id=u310af6d1&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2021-05-17%20%E4%B8%8B%E5%8D%885.13.38.png&originHeight=64&originWidth=182&originalType=binary&ratio=1&size=7751&status=done&style=none&taskId=u7f8a86a8-bffb-4053-8a29-1a2614bddc6)
- 重量级锁
```java
public class Test8 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(100000000);
A a = new A();
long start = System.currentTimeMillis();
for (int i = 0; i < 2; i++) {
new Thread(()->{
while (countDownLatch.getCount() > 0){
a.parse();
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println((end - start) + " ms");
}
}
class A {
int i = 0;
public synchronized void parse(){
i++;
}
}
1.4.4 撤销偏向
以下几种情况会使对象的偏向锁失效
- 调用对象的 hashCode 方法
public class Test10 {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
A a = new A();
a.hashCode();
System.out.println("before lock...");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a) {
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
System.out.println("after locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
before lock...
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 6b 7c 94 (00000001 01101011 01111100 10010100) (-1803785471)
4 4 (object header) 63 00 00 00 (01100011 00000000 00000000 00000000) (99)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 e9 86 0b (10010000 11101001 10000110 00001011) (193390992)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 6b 7c 94 (00000001 01101011 01111100 10010100) (-1803785471)
4 4 (object header) 63 00 00 00 (01100011 00000000 00000000 00000000) (99)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
调用hashcode方法后,会避免成为偏向锁。可以看到。延迟5秒时候第15行不会变成偏向锁101,而是轻量级锁00。hashcode与偏向锁是互斥的。
多个线程使用该对象
调用了 wait/notify 方法(调用wait方法会导致锁膨胀而使用重量级锁)
public class Test10 {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
A a = new A();
System.out.println("before lock...");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
new Thread(()->{
synchronized (a) {
System.out.println("locking");
System.out.println("before wait");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
try {
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after wait");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}, "t1").start();
TimeUnit.SECONDS.sleep(7);
synchronized (a){
a.notifyAll();
}
}
}
before lock...
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
locking
before wait
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 00 89 e6 04 (00000000 10001001 11100110 00000100) (82217216)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after wait
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) fa 99 82 25 (11111010 10011001 10000010 00100101) (629316090)
4 4 (object header) 91 7f 00 00 (10010001 01111111 00000000 00000000) (32657)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
before lock时第5行无锁,before wait是第16行轻量级锁,after wait第26行重量级锁
1.4.5 重偏向问题
1. 单个对象偏向锁升级过程
如果线程是对一个对象加锁,不会发生重偏向情况。单个重偏向没有意义。因为每个线程加偏向锁都来cas判断对象的中的线程id是否是自己的,偏向锁释放后会保留偏向线程id,一直判断相当于轻量级锁。
如果两个线程交替执行(没有竞争),线程1持有对象a的偏向锁,当线程1释放偏向锁时,
- 情况1:如果线程1已经死亡。另一个线程对对象a加锁,那么该锁膨胀为轻量级锁
- 情况2:如果线程1还活着,另一个线程对对象a加锁,那么该锁依然膨胀为轻量级锁。
[注:其实个别情况下会发生偏向情况,但不是重偏向,还是偏向线程1]
情况1验证:线程1已经死亡
// -XX:BiasedLockingStartupDelay=0
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
log.debug("before lock...");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
new Thread(()->{
synchronized (a) {
log.debug("t1 locking");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
}
}, "t1").start();
TimeUnit.SECONDS.sleep(10);
synchronized (a){
log.debug("main locking");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
}
}
}
13:20:12.340 [main] DEBUG c.Test11 - before lock...
13:20:13.081 [main] DEBUG c.Test11 - com.ll.ch8.A 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) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
13:20:13.084 [t1] DEBUG c.Test11 - t1 locking
13:20:13.086 [t1] DEBUG c.Test11 - com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 10 0f 2b (00000101 00010000 00001111 00101011) (722407429)
4 4 (object header) c8 7f 00 00 (11001000 01111111 00000000 00000000) (32712)
8 4 (object header) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
13:20:23.084 [main] DEBUG c.Test11 - main locking
13:20:23.086 [main] DEBUG c.Test11 - com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 b9 cc 01 (10010000 10111001 11001100 00000001) (30194064)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
before lock…之前,第4行处于可偏向状态;t1 locking后,第14行,处于偏向t1状态;10s后main locking后,此时t1已经死亡,第24行,发生膨胀,处于轻量级锁状态。
情况2验证:线程1还活着
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
log.debug("before lock...");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
new Thread(()->{
synchronized (a) {
log.debug("t1 locking");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
}
try {
TimeUnit.SECONDS.sleep(15);
log.debug("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
TimeUnit.SECONDS.sleep(10);
synchronized (a){
log.debug("main locking");
log.debug("{}", ClassLayout.parseInstance(a).toPrintable());
}
}
}
13:25:15.155 [main] DEBUG c.Test11 - before lock...
13:25:15.871 [main] DEBUG c.Test11 - com.ll.ch8.A 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) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
13:25:15.874 [t1] DEBUG c.Test11 - t1 locking
13:25:15.877 [t1] DEBUG c.Test11 - com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 10 02 65 (00000101 00010000 00000010 01100101) (1694633989)
4 4 (object header) a2 7f 00 00 (10100010 01111111 00000000 00000000) (32674)
8 4 (object header) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
13:25:25.878 [main] DEBUG c.Test11 - main locking
13:25:25.879 [main] DEBUG c.Test11 - com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 29 60 08 (10010000 00101001 01100000 00001000) (140519824)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 88 8e 01 f8 (10001000 10001110 00000001 11111000) (-134115704)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
13:25:30.882 [t1] DEBUG c.Test11 - t1 end
第24行main locking的时候线程1还活着,发生膨胀,依然是轻量级锁状态。第31行,此时t1才死亡。
特别情况:单个对象还是会变成偏向锁,偏向线程1(尝试了N次之后)
// -XX:BiasedLockingStartupDelay=0
public class Test13 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread t1 = new Thread(()->{
synchronized (a){
System.out.println("t1 locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("t1 release");
Thread t2= new Thread(()->{
synchronized (a){
System.out.println("t2 locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}, "t2");
t2.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 release");
Thread t3 = new Thread(()->{
synchronized (a){
System.out.println("t3 locking");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}, "t3");
t3.start();
System.out.println("t3 release");
}
}
t1 locking
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c8 8e b9 (00000101 11001000 10001110 10111001) (-1181825019)
4 4 (object header) d9 7f 00 00 (11011001 01111111 00000000 00000000) (32729)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 release
t2 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c8 8e b9 (00000101 11001000 10001110 10111001) (-1181825019)
4 4 (object header) d9 7f 00 00 (11011001 01111111 00000000 00000000) (32729)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2 release
t3 release
t3 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c8 8e b9 (00000101 11001000 10001110 10111001) (-1181825019)
4 4 (object header) d9 7f 00 00 (11011001 01111111 00000000 00000000) (32729)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
第16行和第28行按理说应该是轻量级锁状态,在这里却变成了偏向锁。而且是偏向了同一个线程t1。cpu可能会给他分配同一个线程id。
2. 如何实现重偏向
使用同一个线程对同一个类的很多个对象加锁才会出现重偏向,而偏向锁中的epoch(时间戳)就是记录撤销次数的。 每个class会维护一个epoch,对象也会有该值(建对象时从class中拿),当该class的对象发生偏向撤销时,该计数器会+1,当达到20时,jvm就认为该class的偏向锁有问题,进行批量重偏向。不在同步块中的对象epoch != 当前的epoch,会失效,重偏向将线程id改成新的线程;在同步块中的对象会将其epoch值改成当前的epoch
- 批量重偏向
- 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向 t1 的对象仍有机会重新偏向 t2
- 重偏向会重置Thread ID
- 当撤销超过20次后(超过阈值,第20次开始),JVM 会觉得是不是偏向错了,这时会在给对象加锁时,不会撤销而是重新偏向至该加锁线程。
- 分为两步:
- 将类中的撤销计数器自增1,之后当该类已存在的实例获得锁时,就会尝试重偏向。
- 处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后更新它们的epoch值。也就是说不会重偏向正在使用的锁,否则会破坏锁的线程安全性。
- 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向 t1 的对象仍有机会重新偏向 t2
- 批量重撤销
- 当撤销偏向锁的阈值超过 40(第40次开始) 以后,就会将整个类的剩下的对象都改为不可偏向的,新建的对象也是不可偏向的
- 两步:
- 将类的偏向标记关闭,之后当该类已存在的实例获得锁时,就会升级为轻量级锁;该类新分配的对象的mark word则是无锁模式。
- 处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后撤销偏向锁。
整个过程
- 当线程1对40个对象加偏向锁(101)后,存储该线程1的id, 线程2重新给这40个对象加锁,之后线程3再个这40个对象加锁
- 线程2:
- 前19个对象为撤销偏向,线程2加锁后由原先线程1的偏向锁升级为轻量级锁(00),同步代码块执行完成后变为正常状态(001);
- 从第20个对象开始,达到阈值20,epoch设置为01,剩下的对象epoch还为00,进行批量重偏向,线程2加锁后由原先的线程1的偏向锁变为该线程的偏向锁(101),存储该线程2的id,同步代码块执行完成后状态不变;
- 线程3:
- 前19个对象已经是正常状态,执行加轻量锁(00)和释放锁(001)的。(该对象偏向锁已经失效)
- 从第20个对象开始,撤销偏向,线程3加锁后由原先线程2的偏向锁升级为轻量级锁(00),同步代码块执行完成后变为正常状态(001);
- 从第40个对象开始,达到了阈值40,剩下的对象会撤销偏向,由原先线程2的偏向锁升级为轻量级锁(00),同步代码块执行完成后变为正常状态(001);新建的对象会进入正常状态(001)
代码示例
- 线程2:
- 当线程1对40个对象加偏向锁(101)后,存储该线程1的id, 线程2重新给这40个对象加锁,之后线程3再个这40个对象加锁
第10次(第10个对象) ```java public class Test12 {
public static void main(String[] args) throws InterruptedException {
List<A> list= new ArrayList<>(50);
Thread t1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
A a = new A();
list.add(a);
synchronized (a){
if (i == 9){
System.out.println("t1 locking");
System.out.println(ClassLayout.parseInstance(list.get(9)).toPrintable());
} if (i == 9){
System.out.println("t2 locking");
System.out.println(ClassLayout.parseInstance(list.get(9)).toPrintable());
}
}
}
}, "t1");
System.out.println("--------t1开始----------");
t1.start();
t1.join();
System.out.println("--------t1结束----------");
System.out.println(ClassLayout.parseInstance(list.get(9)).toPrintable());
Thread t2 = new Thread(()->{
for (int i = 0; i < list.size(); i++) {
A a = list.get(i);
synchronized (a){
if (i == 9){
System.out.println("t2 locking");
System.out.println(ClassLayout.parseInstance(list.get(9)).toPrintable());
}
}
}
}, "t2");
System.out.println("--------t2开始----------");
t2.start();
t2.join();
System.out.println("--------t2结束----------");
System.out.println(ClassLayout.parseInstance(list.get(9)).toPrintable());
} }
结果:
```java
--------t1开始----------
t1 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f8 07 86 (00000101 11111000 00000111 10000110) (-2046298107)
4 4 (object header) ed 7f 00 00 (11101101 01111111 00000000 00000000) (32749)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t1结束----------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f8 07 86 (00000101 11111000 00000111 10000110) (-2046298107)
4 4 (object header) ed 7f 00 00 (11101101 01111111 00000000 00000000) (32749)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2开始----------
t2 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 68 ef 10 (11111000 01101000 11101111 00010000) (284125432)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2结束----------
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到t1开始后第5行变成了偏向状态,t1结束后低15行依然是偏向状态,t2开始后,将其膨胀成了轻量级锁状态(26行),之后变成无锁状态(36行)(1-19都是这样,批量重撤销)
- 第20次(第20个对象)
结果:
--------t1开始----------
t1 locking
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 0b b6 (00000101 11100000 00001011 10110110) (-1240735739)
4 4 (object header) f2 7f 00 00 (11110010 01111111 00000000 00000000) (32754)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t1结束----------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 0b b6 (00000101 11100000 00001011 10110110) (-1240735739)
4 4 (object header) f2 7f 00 00 (11110010 01111111 00000000 00000000) (32754)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2开始----------
t2 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 61 a0 b6 (00000101 01100001 10100000 10110110) (-1231003387)
4 4 (object header) f2 7f 00 00 (11110010 01111111 00000000 00000000) (32754)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2结束----------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 61 a0 b6 (00000101 01100001 10100000 10110110) (-1231003387)
4 4 (object header) f2 7f 00 00 (11110010 01111111 00000000 00000000) (32754)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到第27行和37行依然是偏向锁,只不过偏向线程id改变了(20-39都会是这样,重新偏向该线程)
第35次(第35个对象) ```java public class Test12 {
public static void main(String[] args) throws InterruptedException {
List<A> list= new ArrayList<>(50);
Thread t1 = new Thread(()->{
for (int i = 0; i < 50; i++) {
A a = new A();
list.add(a);
synchronized (a){
if (i == 35){
System.out.println("t1 locking");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
}
}
}
}, "t1");
System.out.println("--------t1开始----------");
t1.start();
t1.join();
System.out.println("--------t1结束----------");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
Thread t2 = new Thread(()->{
for (int i = 0; i < list.size(); i++) {
A a = list.get(i);
synchronized (a){
if (i == 35){
System.out.println("t2 locking");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
}
}
}
}, "t2");
System.out.println("--------t2开始----------");
t2.start();
t2.join();
System.out.println("--------t2结束----------");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
Thread t3 = new Thread(()->{
for (int i = 0; i < list.size(); i++) {
A a = list.get(i);
synchronized (a){
if (i == 35){
System.out.println("t3 locking");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
}
}
}
}, "t3");
System.out.println("--------t3开始----------");
t3.start();
t3.join();
System.out.println("--------t3结束----------");
System.out.println(ClassLayout.parseInstance(list.get(35)).toPrintable());
}
}
```java
--------t1开始----------
t1 locking
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 83 9e (00000101 10110000 10000011 10011110) (-1635536891)
4 4 (object header) e4 7f 00 00 (11100100 01111111 00000000 00000000) (32740)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t1结束----------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 83 9e (00000101 10110000 10000011 10011110) (-1635536891)
4 4 (object header) e4 7f 00 00 (11100100 01111111 00000000 00000000) (32740)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2开始----------
t2 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 19 09 9e (00000101 00011001 00001001 10011110) (-1643570939)
4 4 (object header) e4 7f 00 00 (11100100 01111111 00000000 00000000) (32740)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t2结束----------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 19 09 9e (00000101 00011001 00001001 10011110) (-1643570939)
4 4 (object header) e4 7f 00 00 (11100100 01111111 00000000 00000000) (32740)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t3开始----------
t3 locking
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 f8 0e 10 (11111000 11111000 00001110 00010000) (269416696)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
--------t3结束----------
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到6,16行偏向线程t1;27行,37行偏向线程t2;48行开始膨胀成轻量级锁。
2 锁的膨胀机制
2.1 锁的膨胀过程举例
public class Test9 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
System.out.println("------before lock------");
System.out.println(ClassLayout.parseInstance(a).toPrintable()); //无锁
Thread t1 = new Thread(()->{
synchronized (a) {
try {
System.out.println("------t1 locking------");
System.out.println(ClassLayout.parseInstance(a).toPrintable()); // 轻量级锁
TimeUnit.SECONDS.sleep(5);
System.out.println("------t1 release------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
synchronized (a){
System.out.println("------main locking------");
System.out.println(ClassLayout.parseInstance(a).toPrintable()); // 重量级锁
System.out.println("------main release------");
}
System.out.println("------after lock------");
System.out.println(ClassLayout.parseInstance(a).toPrintable()); // 重量级锁
System.gc();
System.out.println("------after gc------");
System.out.println(ClassLayout.parseInstance(a).toPrintable()); // 无锁,GC加1
}
}
------before lock------
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.ll.ch8.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
------t1 locking------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 00 c9 6c 10 (00000000 11001001 01101100 00010000) (275564800)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
------t1 release------
------main locking------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 4a 5f 81 85 (01001010 01011111 10000001 10000101) (-2055119030)
4 4 (object header) f9 7f 00 00 (11111001 01111111 00000000 00000000) (32761)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
------main release------
------after lock------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 4a 5f 81 85 (01001010 01011111 10000001 10000101) (-2055119030)
4 4 (object header) f9 7f 00 00 (11111001 01111111 00000000 00000000) (32761)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
------after gc------
com.ll.ch8.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
注意在gc过后,值为00001001,其中gc年龄为0001,发生了变化。
2.2 偏向锁升级
匿名偏向状态是偏向锁的初始状态,在这个状态下第一个试图获取该对象的锁的线程,会使用CAS操作(汇编命令CMPXCHG)尝试将自己的threadID写入对象头的mark word中,使匿名偏向状态升级为已偏向(Biased)的偏向锁状态。在已偏向状态下,线程指针threadID非空,且偏向锁的时间戳epoch为有效值。
如果之后有线程再次尝试获取锁时,需要检查mark word中存储的threadID是否与自己相同即可,如果相同那么表示当前线程已经获得了对象的锁,不需要再使用CAS操作来进行加锁。
如果mark word中存储的threadID与当前线程不同,那么将执行CAS操作,试图将当前线程的ID替换mark word中的threadID。只有当对象处于下面两种状态中时,才可以执行成功:
- 对象处于匿名偏向状态
- 对象处于可重偏向(Rebiasable)状态,新线程可使用CAS将threadID指向自己
如果对象不处于上面两个状态,说明锁存在线程竞争,在CAS替换失败后会执行偏向锁撤销操作。偏向锁的撤销需要等待全局安全点Safe Point(安全点是 jvm为了保证在垃圾回收的过程中引用关系不会发生变化设置的安全状态,在这个状态上会暂停所有线程工作),在这个安全点会挂起获得偏向锁的线程。
在暂停线程后,会通过遍历当前jvm的所有线程的方式,检查持有偏向锁的线程状态是否存活:
- 如果线程还存活,且线程正在执行同步代码块中的代码,则升级为轻量级锁
- 如果持有偏向锁的线程未存活,或者持有偏向锁的线程未在执行同步代码块中的代码,则进行校验是否允许重偏向:
- 不允许重偏向,则撤销偏向锁,将mark word升级为轻量级锁,进行CAS竞争锁
- 允许重偏向,设置为匿名偏向锁状态,CAS将偏向锁重新指向新线程
-
3. 可重入锁
3.1 说明
可重入锁又名递归锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
- Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。即自己可以获取自己的内部锁。
3.2 种类
隐式锁(即synchronized关键字使用的锁)默认是可重入锁
- 显式锁(即Lock)也有ReentrantLock这样的可重入锁。(reentrantLock注意加了几次锁,就要释放几次锁,否则容易造成死锁别的线程拿不到)