参考
https://blog.csdn.net/qq_35190492/article/details/106180781
https://www.cnblogs.com/lukelook/p/9946065.html
https://baijiahao.baidu.com/s?id=1612142459503895416
Synchronized 使用
- 修饰方法
public synchronized void method(){
}
synchronized 关键字不能继承。
- 修饰代码块
synchronized(this){
}
synchronized(obj){
}
当一个线程访问一个对象中的 synchronized(this) 同步代码块时,其他试图访问该对象的线程将被阻塞(多个线程可以访问同一个对象,this 指当前对象)
用 synchronized 给 obj 对象加锁,当一个线程访问 obj 对象时,其他试图访问 obj 对象的线程将会阻塞
- 修饰类
synchronized(MyClass.class)
该类的所有对象公用一把锁
java对象在 JVM内存模型 中的构成
-
对象头
-
Mark Word(标记字段)
默认存储对象的 HashCode,分代年龄 和 锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。 -
Klass Point(类型指针)
对象指向它的 类 元数据的指针,虚拟机通过这个指针来确定这个对象是 哪个类的实例。
-
-
实例数据
这部分主要是存放类的数据信息,父类的信息。 -
对齐填充
由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
一个空对象占多少个字节?就是8个字节,是因为对齐填充的关系,不到8个字节对其填充会帮我们自动补齐。
synchronized 同步代码块 的底层原理
synchronized 锁住对象,其指针指向该对象的 monitor。
每个对象实例都会有一个 monitor。其中 monitor 可以与对象一起创建、销毁;或者当线程试图获取对象锁时自动生成。
monitor是由 ObjectMonitor 实现(ObjectMonitor.hpp文件)(.hpp 是 C++ 的头文件后缀)
_WaitSet 用来保存每个等待锁的线程对象。
_owner,它指向持有ObjectMonitor对象的线程。
当多个线程同时访问一段同步代码时,会先存放到 _EntryList 集合中,接下来当线程获取到对象的 monitor 时,就会把 _owner 变量设置为当前线程。同时 count变量+1。
如果线程调用wait() 方法,就会释放当前持有的monitor,那么 _owner 变量就会被置为null,同时 _count减1,并且该线程进入 WaitSet集合中,等待下一次被唤醒。
因为这个锁对象存放在对象本身,也就是为什么Java中任意对象可以作为锁的原因。
字节码中在执行 monitorenter 指令时,首先要尝试获取对象锁,也就是上文提到的 monitor 对象。如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,那么就把锁的计数器(_count)加1。当然与之对应执行 monitorexit 指令时,锁的计数器(_count)也会减1。
如果当前线程获取锁失败,那么就会被阻塞住,进入 _WaitSet 中,等待锁被释放为止。
字节码中,有俩个 monitorexit 指令的原因是:编译器需要确保方法中调用过的每条 monitorenter 指令都要执行对应的monitorexit 指令。为了保证在方法异常时,monitorenter和monitorexit指令也能正常配对执行,编译器会多写一个 monitorexit 指令
synchronized 同步方法 的底层原理
字节码中并没有 monitorenter指令和 monitorexit指令,取得代之的是 ACC_SYNCHRONIZED 标识
JVM通过 ACC_SYNCHRONIZED 标识,就可以知道这是一个需要同步的方法,进而执行上述同步的过程,也就是 _count加1 的这些过程。