遇到线程安全的时候需要使用synchronized
Synchronized关联到一个对象:
1,如果在普通方法上加上synchronized 当前this 锁;
2, 如果在静态方法上加上synchronized 当前class字节码
3,或者自定义锁对象
synchronized是一个java的关键字:
java 代码转c代码 转汇编代码;
synchronized 重入锁
package com.taotao.metithread;
/**
*@author tom
*Date 2020/7/20 0020 7:36
*synchronized 重入锁
*/
public class Test005 extends Thread{
private Object lookobject=new Object();
@Override
public void run() {
a();
}
private void a() {
synchronized (lookobject){
System.out.println("我是a调用b");
b();
}
}
private void b() {
synchronized (lookobject){
System.out.println("我是b");
}
}
public static void main(String[] args) {
new Test005().start();
}
}
java 代码如何变成汇编代码 javap -p -v class 文件
java编译class文件,
Monitorenter/Monitorexit
monitorenter
- monitorenter官方解释:
<u>https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter</u>
翻译过来:
每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获
取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:
1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直
到monitor的进入数变为0,才能重新尝试获取monitor的所有权。
monitorenter小结:
synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待
Monit才是真正的锁,是一个c++对象
Owner 拥有锁的线程
Recursions 记录获取锁的次数
monitorexit
<u>https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorexit</u>
翻译过来:
1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个
monitor的所有权
monitorexit释放锁。
monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。
为什么会有两个monitorexit,因为 Synchronized锁的同步代码块如果抛出异常的情况下,则自动释放锁。
通过monitorexit 去监听锁;1.判断_owner 是否为空,空则获取锁,其他线程进行阻塞, _recursions 归数加一,在进入另外个锁的时候在进行判断 _owner如果_owener相同,递归数_recursions加1,
当退出锁时 递归数减1;
一个同步代码块里有2个退出(monitorexit),一个是正常退出,一个是异常退出;
在Java虚拟机(HotSpot)中,monitor是有ObjectMonitor实现的(C++实现的,位于HotSpot虚拟机源码\openjdk8\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.hpp文件),其主要数据结构如下
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0; // 递归次数/重入次数
_object = NULL;
_owner = NULL; // 记录当前持有锁的线程ID
_WaitSet = NULL; // 等待池:处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 锁池:处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
站在虚拟机源码分析
<u>http://hg.openjdk.java.net/jdk8</u> 下载hotspot虚拟机
_Owner 记录当前监视器被哪个线程持有, 要么是空或者当前自己,
_exq 竞争:排列正在获取锁的线程(当前我们的锁没有被任何线程持有)
_EntryList锁池:当前的锁已经被其他的线程持有,存放都是没有获取锁正在阻塞的线程。
_waitSet: 等待池 通过锁wait 方法,当前的线程会变为阻塞状态放入到_waitet
在调用notify 方法的 唤醒 等待 池中的正在等待的线程,到锁池中等待池-锁池
当T1被唤醒后需要重新竞争
java 对象布局: 对象头(Mark word /class 指针),实例数据,填充数据
package com.taotao.metithread;
import org.openjdk.jol.info.ClassLayout;
/**
*@author tom
*Date 2020/7/20 0020 9:21
*查看类型占的字节情况
*/
public class MeiteLock {
private int userId;
public static void main(String[] args) {
MeiteLock meiteLock=new MeiteLock();
System.out.println(ClassLayout.parseInstance(meiteLock).toPrintable());
}
}
image.png
分析: 对象头 16个字节 实例属性4个-20个字节/8+填充4个 24/8
new对象请问占用多少字节? 对象头(16) ,没有压缩的情况下+实例属性(填充)
基本数据类型占多少字节
1、bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
2、byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。
64位/8
1Byte=8bit (1B=8bit)
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB
int 32bit 4
short 16bit 2
long 64bit 8
byte 8bit
char 16bit
float 32bit
double 64bit
boolean 1bit
网友评论