Synchronized原理图
2022-01-27 11:43:41 0 举报
Synchronized原理图
作者其他创作
大纲/内容
Java对象实例(instanceOopDesc)
开始新一轮竞争
Synchronized
自旋达到一定次数CAS操作作依然没有成功
锁消除
JAVA线程状态
原持有偏向锁的线程的栈中分配锁记录
轻量级锁
new
原持有偏向锁线程获得轻量级锁指向原持有偏向锁线程锁记录的指针 | 00(标志位)
Contention List:所有请求锁的线程将被首先放置到该竞争队列Entry List:Contention List中那些有资格成为候选人的线程被移到Entry ListWait Set:那些调用wait方法被阻塞的线程被放置到Wait SetOnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeckOwner:获得锁的线程称为Owner!Owner:释放锁的线程
偏向锁
指向重量级锁monitor的指针ptr to heavyweight monitor
ObjectMonitor内部运行机制
依赖mutex(操作系统的互斥)
原持有偏向锁线程释放锁空| 0(是否偏向锁) | 01(标志位)
Java堆
线程访问同步代码块
执行同步代码块
时间结束
检查对象头的Mark Word中记录的是否是当前线程ID?
6
暂停原持有偏向锁的线程
Thread.yield()
拷贝对象头中的Mark Word到原持有偏向锁线程的锁记录中
升级为轻量级锁
00
对象内存中存储布局
是
01
Waiting Queue
BockingQueue
锁
优点
缺点
适用场景
加锁和解锁不需要额外的消耗,和执行非同步的方法比仅存在纳秒级的差距
如果线程间存在竞争,会带来额外的锁撤消的消耗
适用于只有一个线程访问同步块场景
竞争的线程不会阻塞,提高了程序的响应速度
如果始终得不到锁竞争的线程使用自旋会消耗CPU
追求响应时间,锁占用时间很短
重量级锁
线程竞争不使用自旋,不会消耗CPU
线程阻塞,响应时间缓慢
追求吞吐量,锁占用时间长
1(是偏向锁)
mutex挂起当前线程
Java对象
失败
是否偏向锁?
开始轻量级解锁
Java数组实例(objArrayOopDesc或 typeArrayOopDesc)
无锁
1
OnDeck
Java栈
int
reference
1.JOL工具: pom中加入依赖 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>2.JCStress压测工具:1.引入骨架项目: mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jcstress -DarchetypeArtifactId=jcstress-java-test-archetype -DarchetypeVersion=0.5 -Dg roupId=exam.offer -DartifactId=testJcstress -Dversion=1.0 2. mvn clean install 3.java -jar targer\\jcstress.jar
原持有锁的线程到达安全点
WaitSet
工具
锁粗化
特征
指向栈中锁记录的指针ptr to lock record
start()
未活动状态/已退出同步代码块
CAS操作将对象头的Mark Word 中锁记录指针指向当前线程锁记录
锁状态
不是
自旋
实例数据
CAS操作替换Thread id
_mark : markOop
_klass : klassOop
_length : int
...array data....
padding
Ready Thread
Runnig Thread
1.通过栈帧中的对象引用reference找到Java堆中的对象,再通过对象的instanceOop中的元数据指针klass来找到方法区中的instanceKlass,从而确定该对象的类型。2.执行new A()的时候,JVM 做了什么工作。首先,如果这个类没有被加载过,JVM就会进行类的加载,并在JVM内部创建一个instanceKlass对象表示这个类的运行时元数据(相当于Java层的Class对象)。初始化对象的时候(执行invokespecial A::),JVM就会创建一个instanceOopDesc对象表示这个对象的实例,然后进行Mark Word的填充,将元数据指针指向Klass对象,并填充实例变量。3.元数据—— instanceKlass 对象会存在元空间(方法区),而对象实例—— instanceOopDesc 会存在Java堆。Java虚拟机栈中会存有这个对象实例的引用。
轻量级锁00(标志位)
对齐填充
成功
4
5
唤醒原持有偏向锁的线程
epoch
可GC
用于标记GC
EntryList
...instance data....
只需比较Thread ID
TimedWaiting
对象头
01(标志位)
blocked
o.notify()o.notifyAll()LockSupport.unpark()
当前线程
从安全点继续执行
-
目前锁状态?
线程被挂起
获得轻量级锁指向 当前线程锁记录的指针 | 00(标志位)
执行时间短(加锁代码),线程竞争不激烈,用自旋执行时间长,争用线程数多,用系统锁
Klass Pointer ( _klass : klassOop )
instanceOopDesc
对象指向它的类元数据的指针,虚拟机可以通过这个指针来确定这个对象是哪个类的实例数组、对象头中还必须有一块用于记录数组长度的数据因为虚拟机可以通过普通JAVA对象的元数据信息确定JAVA对象的大小,但是从数组的元数据中无法确定数组的大小。
1bit
11
空
对象头(header)
25bit
被线程调度器执行
2bit
bitfields
Teminated
Mark Word ( _mark : markOop )
唤醒那些被挂起的线程
检查持有偏向锁的线程状态
原持有偏向锁线程
CAS操作1.对象头中的Mark Word中锁记录指针是否仍然指向当前线程锁记录2.拷贝在当前线程锁记录的Mark Word信息是否与对象头中的Mark Word一致
Running
age
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
重量级锁10(标志位)
获得锁
升级为重量级锁指向重量级锁monitor的指针 | 10(标志位)
Thread.sleep(time)o.wait(time)t.join(time)LockSupport.parkNanos()LockSupport.parkUntil()
未退出同步代码块
Thread ID
JAVA对象
自旋锁
NEW
释放锁
Synchronized内部原理
o.wait()t.join()LockSupport.park()
instanceKlass
hashcode
ContentionList
对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
2
0(否)
开始偏向锁撤消(等待竞争出现才释放锁的机制)
自适应自旋锁
Ready
3
10
1(是)
再尝试
4bit
Owner
waiting
23bit
锁的对比
方法区
偏向锁撤消
线程调度器选中执行
获得对象锁Thread id | epoch | age | 1(是否偏向锁) | 01(标志位)
标志位
0(是否偏向锁)
当前线程的栈中分配锁记录
收藏
收藏
0 条评论
下一页